gevent-socketio-0.3.6/0000755000175000017500000000000012273622372015464 5ustar sonteksontek00000000000000gevent-socketio-0.3.6/setup.py0000644000175000017500000000310012273622336017170 0ustar sonteksontek00000000000000from setuptools import setup from setuptools import find_packages from setuptools.command.test import test as TestCommand class PyTest(TestCommand): def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [] self.test_suite = True def run_tests(self): #import here, cause outside the eggs aren't loaded import pytest pytest.main(self.test_args) setup( name="gevent-socketio", version="0.3.6", description=( "SocketIO server based on the Gevent pywsgi server, " "a Python network library"), author="Jeffrey Gelens", author_email="jeffrey@noppo.pro", maintainer="Alexandre Bourget", maintainer_email="alex@bourget.cc", license="BSD", url="https://github.com/abourget/gevent-socketio", download_url="https://github.com/abourget/gevent-socketio", install_requires=("gevent", "gevent-websocket",), setup_requires=('versiontools >= 1.7'), cmdclass = {'test': PyTest}, tests_require=['pytest', 'mock'], packages=find_packages(exclude=["examples", "tests"]), classifiers=[ "Development Status :: 4 - Beta", "License :: OSI Approved :: BSD License", "Programming Language :: Python", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX", "Topic :: Internet", "Topic :: Software Development :: Libraries :: Python Modules", "Intended Audience :: Developers", ], entry_points=""" [paste.server_runner] paster = socketio.server:serve_paste """, ) gevent-socketio-0.3.6/gevent_socketio.egg-info/0000755000175000017500000000000012273622372022346 5ustar sonteksontek00000000000000gevent-socketio-0.3.6/gevent_socketio.egg-info/SOURCES.txt0000644000175000017500000000224612273622372024236 0ustar sonteksontek00000000000000AUTHORS CHANGELOG LICENSE MANIFEST.in README.rst setup.py docs/Makefile docs/make.bat docs/source/conf.py docs/source/handler.rst docs/source/index.rst docs/source/main.rst docs/source/mixins.rst docs/source/namespace.rst docs/source/packet.rst docs/source/server.rst docs/source/server_integration.rst docs/source/sgunicorn.rst docs/source/transports.rst docs/source/virtsocket.rst gevent_socketio.egg-info/PKG-INFO gevent_socketio.egg-info/SOURCES.txt gevent_socketio.egg-info/dependency_links.txt gevent_socketio.egg-info/entry_points.txt gevent_socketio.egg-info/requires.txt gevent_socketio.egg-info/top_level.txt socketio/__init__.py socketio/defaultjson.py socketio/exceptions.py socketio/handler.py socketio/mixins.py socketio/namespace.py socketio/packet.py socketio/policyserver.py socketio/sdjango.py socketio/server.py socketio/sgunicorn.py socketio/storage.py socketio/transports.py socketio/virtsocket.py tests/__init__.py tests/test_namespace.py tests/test_packet.py tests/test_socket.py tests/jstests/jstests.py tests/jstests/static/WebSocketMain.swf tests/jstests/static/qunit.css tests/jstests/static/qunit.js tests/jstests/static/socket.io.js tests/jstests/tests/suite.jsgevent-socketio-0.3.6/gevent_socketio.egg-info/entry_points.txt0000644000175000017500000000011212273622372025636 0ustar sonteksontek00000000000000 [paste.server_runner] paster = socketio.server:serve_paste gevent-socketio-0.3.6/gevent_socketio.egg-info/PKG-INFO0000644000175000017500000000134012273622372023441 0ustar sonteksontek00000000000000Metadata-Version: 1.1 Name: gevent-socketio Version: 0.3.6 Summary: SocketIO server based on the Gevent pywsgi server, a Python network library Home-page: https://github.com/abourget/gevent-socketio Author: Alexandre Bourget Author-email: alex@bourget.cc License: BSD Download-URL: https://github.com/abourget/gevent-socketio Description: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: POSIX Classifier: Topic :: Internet Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Intended Audience :: Developers gevent-socketio-0.3.6/gevent_socketio.egg-info/dependency_links.txt0000644000175000017500000000000112273622372026414 0ustar sonteksontek00000000000000 gevent-socketio-0.3.6/gevent_socketio.egg-info/requires.txt0000644000175000017500000000002712273622372024745 0ustar sonteksontek00000000000000gevent gevent-websocketgevent-socketio-0.3.6/gevent_socketio.egg-info/top_level.txt0000644000175000017500000000001112273622372025070 0ustar sonteksontek00000000000000socketio gevent-socketio-0.3.6/AUTHORS0000644000175000017500000000047212261120545016527 0ustar sonteksontek00000000000000This SocketIO server based on Gevent and was written by: Jeffrey Gelens Current Maintainers: Alexandre Bourget John Anderson Contributors: Denis Bilenko Sébastien Béal jpellerin (JP) Philip Neustrom gevent-socketio-0.3.6/docs/0000755000175000017500000000000012273622372016414 5ustar sonteksontek00000000000000gevent-socketio-0.3.6/docs/make.bat0000644000175000017500000001200312261120545020005 0ustar sonteksontek00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source set I18NSPHINXOPTS=%SPHINXOPTS% source if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\gevent-socketio.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\gevent-socketio.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end gevent-socketio-0.3.6/docs/Makefile0000644000175000017500000001275112261120545020052 0ustar sonteksontek00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/gevent-socketio.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/gevent-socketio.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/gevent-socketio" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/gevent-socketio" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." gevent-socketio-0.3.6/docs/source/0000755000175000017500000000000012273622372017714 5ustar sonteksontek00000000000000gevent-socketio-0.3.6/docs/source/sgunicorn.rst0000644000175000017500000000024712273550306022455 0ustar sonteksontek00000000000000.. _gunicorn_module: :mod:`socketio.sgunicorn` ========================= .. automodule:: socketio.sgunicorn :members: :undoc-members: :show-inheritance: gevent-socketio-0.3.6/docs/source/transports.rst0000644000175000017500000000060012273550306022656 0ustar sonteksontek00000000000000.. _transports_module: :mod:`socketio.transports` ========================== This is largely an internal module, responsible for translating the different fallback mechanisms to one abstracted Socket, dealing with payload encoding, multi-message multiplexing and their reverse operation. .. automodule:: socketio.transports :members: :undoc-members: :show-inheritance: gevent-socketio-0.3.6/docs/source/handler.rst0000644000175000017500000000040012273550306022052 0ustar sonteksontek00000000000000.. _handler_module: :mod:`socketio.handler` ======================= This is a lower-level transports handler. It is responsible for calling your WSGI application. .. automodule:: socketio.handler :members: :undoc-members: :show-inheritance: gevent-socketio-0.3.6/docs/source/main.rst0000644000175000017500000000051512261120545021363 0ustar sonteksontek00000000000000.. _main_module: :mod:`socketio` =============== This module holds the main hooking function for your framework of choice. Call the `socketio_manage` function from a view in your framework and this will be the beginning of your Socket.IO journey. .. automodule:: socketio :members: :undoc-members: :show-inheritance: gevent-socketio-0.3.6/docs/source/index.rst0000644000175000017500000002727012261120545021555 0ustar sonteksontek00000000000000.. gevent-socketio documentation master file, created by sphinx-quickstart on Tue Mar 13 20:43:40 2012. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Gevent-socketio documentation ============================= .. toctree:: :maxdepth: 2 Introduction ------------ Socket.IO is a WebSocket-like abstraction that enables real-time communication between a browser and a server. ``gevent-socketio`` is a Python implementation of the protocol. The reference server implementation of Socket.IO runs on Node.js and was developed by LearnBoost. There are now server implementations in a variety of languages. One aim of this project is to provide a single ``gevent``-based API that works across the different WSGI-based web frameworks out there (Pyramid, Pylons, Flask, web2py, Django, etc...). Only ~3 lines of code are required to tie-in ``gevent-socketio`` in your framework. Note: you need to use the ``gevent`` python WSGI server to use ``gevent-socketio``. **Namespaces**: since you mostly have **one** websocket/socket.io endpoint per website, it is important to be able to namespace the different real-time activities of the different pages or parts of your site, just like you need routes to map URLs to different parts of your code. The Socket.IO 0.7+ namespaces are a welcome addition, and if you don't use Socket.IO, you'll probably end-up writing your own namespacing mechanism at some point. **Named events**: To distinguish the messages that are coming and going, you most probably want to give them some name. Here again, not using Socket.IO, you will find yourself implementing a way to tag your packets with names representing different tasks or actions to perform. With Socket.IO 0.6 or with normal WebSockets, you would probably encode a JSON object with one of the keys that is reserved for that (I used ``{"type": "submit_something"}``. Socket.IO 0.7+ implements named events, which put that information in a terse form on the wire. It also allows you to define callbacks, that can be acknowledged by the other endpoint, and then fire back your function with some return parameters. Something great for RPC, that you'd need to implement yourself the moment you need it. **Transports**: One of the main feature of Socket.IO is the abstraction of the transport, that gives you real-time web support down to Internet Explorer 6.0, using long-polling methods. It will also use native WebSockets when available to the browser, for even lower latencies. Currently supported transports: ``websocket``, ``flashsocket``, ``htmlfile``, ``xhr-multipart``, ``xhr-polling``, ``jsonp-polling``. This implementation covers nearly all the features of the Socket.IO 0.7+ (up to at least 0.9.1) protocol, with events, callbacks. It adds security in a pythonic way with granular ACLs (which don't exist in the Node.js version) at the method level. The project has several examples in the source code and in the documentation. Any addition and fixes to the docs are warmly welcomed. Concepts -------- In order to understand the following documentation articles, let's clarify some of the terms used: A **Namespace** is like a controller in the MVC world. It encompasses a set of methods that are logically in it. For example, the ``send_private_message`` event would be in the ``/chat`` namespace, as well as the ``kick_ban`` event. Whereas the ``scan_files`` event would be in the ``/filesystem`` namespace. Each namespace is represented by a sub-class of :class:`BaseNamespace`. A simple example would be, on the client side (the browser): .. code-block:: javascript var socket = io.connect("/chat"); having loaded the ``socket.io.js`` library somewhere in your . On the server (this is a Pyramid example, but its pretty much the same for other frameworks): .. code-block:: python from socketio.namespace import BaseNamespace class ChatNamespace(BaseNamespace): def on_chat(self, msg): self.emit('chat', msg) def socketio_service(request): socketio_manage(request.environ, {'/chat': ChatNamespace}, request) return "out" Here we use :func:`socketio.socketio_manage` to start the Socket.IO machine, and handle the real-time communication. You will come across the notion of a ``Socket``. This is a virtual socket, that abstracts the fact that some transports are long-polling and others are stateful (like a Websocket), and exposes the same functionality for all. You can have many namespaces inside a Socket, each delimited by their name like ``/chat``, ``/filesystem`` or ``/foobar``. Note also that there is a global namespace, identified by an empty string. Some times, the global namespace has special features, for backwards compatibilty reasons (we only have a global namespace in version 0.6 of the protocol). For example, disconnecting the global namespace means disconnect the full socket. Disconnecting a qualified namespace, on the other hand, only removes access to that namespace. The ``Socket`` is responsible from taking the `packets`, which are, in the realm of a ``Namespace`` or a ``Socket`` object, a dictionary that looks like: .. code-block:: python {"type": "event", "name": "launch_superhero", "args": ["Superman", 123, "km", {"hair_color": "brown"}]} These packets are serialized in a compact form when its time to put them on the wire. Socket.IO also has some optimizations if we need to send many packets on some long-polling transports. At this point, if you don't know ``gevent``, you probably will want to learn a bit more about it, since it is the base you will be working on: http://www.gevent.org/ Getting started --------------- Until we have a fully-fledged tutorial, please check out our example applications and the API documentation. You can see a video that shows ``gevent-socketio`` in a live coding presentation here: http://pyvideo.org/video/1573/gevent-socketio-cross-framework-real-time-web-li To learn how to build your Namespace (the object dealing with requests and replies), see: :ref:`namespace_module` See this doc for different servers integration: :ref:`server_integration` Examples -------- The ``gevent-socketio`` repository holds several examples: https://github.com/abourget/gevent-socketio/tree/master/examples * ``simple_chat`` is a bare-bone WSGI app with a minimal socketio integration * ``simple_pyramid_chat`` is a simple chat application built on Pyramid * ``live_cpu_graph`` is a simple realtime CPU graph (linux only) * ``twitter_stream`` is a streaming feed of twitter updates * ``pyramid_backbone_redis_chat`` is a Pyramid app using backbone.js and redis for pubsub * ``pyramid_backbone_redis_chat_persistence`` is a Pyramid app using backbone.js, redis for pubsub and features persistence * ``testapp`` is the app we use to test the different features, so there are a couple of more advanced use-cases demonstrated there ``pyvore`` is an application that was developed to serve as real-time chat in conferences like the PyCon: https://github.com/sontek/pyvore This app is a Django tic-tac-toe application that uses the latest ``gevent-socketio``: https://github.com/sontek/django-tictactoe Security -------- ``gevent-socketio`` provides method-level security, using an ACL model. You can read more about it in the :ref:`namespace_module`, but a basic example to secure one namespace would look like: .. code-block:: python class AdminInterface(BaseNamespace): def get_initial_acl(self): """Everything is locked at first""" return [] def initialize(self): # This here assumes you have passed in a `request` # to your socketio_manage() call, it has that # `is_admin` attribute if not request.is_admin: return else: self.lift_acl_restrictions() def on_blahblahblah(self, data): """This can't be access until `lift_acl_restrictions()` has been called """ pass API docs -------- API documentation is where most of the juice/meat is. Read through and you'll (hopefully) understand everything you need about ``gevent-socketio``. The manager is the function you call from your framework. It is in: :mod:`socketio` **Namespaces** are the main interface the developer is going to use. You mostly define your own BaseNamespace derivatives, and gevent-socketio maps the incoming messages to your methods automatically: :mod:`socketio.namespace` **Mixins** are components you can add to your namespaces, to provided added functionality. :mod:`socketio.mixins` **Sockets** are the virtual tunnels that are established and abstracted by the different Transports. They basically expose socket-like send/receive functionality to the Namespace objects. Even when we use long-polling transports, only one Socket is created per browser. :mod:`socketio.virtsocket` **Packet** is a library that handle the decoding of the messages encoded in the Socket.IO dialect. They take dictionaries for encoding, and return decoded dictionaries also. :mod:`socketio.packet` **Handler** is a lower-level transports handler. It is responsible for calling your WSGI application :mod:`socketio.handler` **Transports** are responsible for translating the different fallback mechanisms to one abstracted Socket, dealing with payload encoding, multi-message multiplexing and their reverse operation. :mod:`socketio.transports` **Server** is the component used to hook Gevent and its WSGI server to the WSGI app to be served, while dispatching any Socket.IO related activities to the `handler` and the `transports`. :mod:`socketio.server` Auto-generated indexes: * :ref:`genindex` * :ref:`modindex` References ---------- LearnBoost's node.js version is the reference implementation, you can find the server component at this address: https://github.com/learnboost/socket.io The client JavaScript library's development branch is here: https://github.com/LearnBoost/socket.io-client The specifications to the protocol are somehow in this repository: https://github.com/LearnBoost/socket.io-spec This is the original wow-website: http://socket.io Here is a list of the different frameworks integration to date, although not all have upgraded to the latest version of gevent-socketio: * pyramid_socketio: https://github.com/abourget/pyramid_socketio * django-socketio: https://github.com/stephenmcd/django-socketio The Flask guys will be working on an integration layer soon. Contacts -------- For any questions, you can use the Issue tracking at Github: https://github.com/abourget/gevent-socketio https://github.com/abourget/gevent-socketio/issues The mailing list: https://groups.google.com/forum/#!forum/gevent-socketio The maintainers: https://twitter.com/bourgetalexndre https://twitter.com/sontek Credits ------- **Jeffrey Gellens** for starting and polishing this project over the years. PyCon 2012 and the Sprints, for bringing this project up to version 0.9 of the protocol. Current maintainers: * Alexandre Bourget * John Anderson Contributors: * Denis Bilenko * Bobby Powers * Lon Ingram * Eugene Baumstein * Sébastien Béal * jpellerin (JP) * Philip Neustrom * Jonas Obrist * fabiodive * Dan O'Neill * Whit Morriss * Chakib (spike) Benziane * Vivek Venugopalan * Vladimir Protasov * Bruno Bigras * Gabriel de Labacheliere * Flavio Curella * thapar * Marconi Moreto * sv1jsb * Cliff Xuan * Matt Billenstein * Rolo * Anthony Oliver * Pierre Giraud * m0sth8 * Daniel Swarbrick TODO ---- How to integrate your framework's "session" object (Beaker, memcached, or file-based). Beware: this can be tricky. You need to manage that yourself. gevent-socketio-0.3.6/docs/source/namespace.rst0000644000175000017500000002006412261120545022374 0ustar sonteksontek00000000000000.. _namespace_module: :mod:`socketio.namespace` ========================= .. automodule:: socketio.namespace .. autoclass:: BaseNamespace Namespace initialization ------------------------ You can override this method: .. automethod:: BaseNamespace.initialize Event flow ---------- This is an attempt at catching the gotchas of the Socket.IO protocol, which, for historical reasons, sometimes have weird event flow. The first function to fire is ``initialize()``, which will be called only if there is an incoming packet for the Namespace. A successful javascript call to ``io.connect()`` **is not** sufficient for ``gevent-socketio`` to trigger the creation of a Namespace object. Some event has to flow from the client to the server. The connection will appear to have succeeded from the client's side, but that is because ``gevent-socketio`` maintains the virtual socket up and running before it hits your application. This is why it is a good pratice to send a packet (often a ``login``, or ``subscribe`` or ``connect`` JSON event, with ``io.emit()`` in the browser). If you're using the GLOBAL_NS, the ``recv_connect()`` will not fire on your namespace, because when the connection is opened, there is no such packet sent. The ``connect`` packet is only sent over (and explicitly sent) by the javascript client when it tries to communicate with some "non-global" namespaces. That is why it is recommended to always use namespaces, to avoid having a different behavior for your different namespaces. It also makes things explicit in your application, when you have something such as ``/chat``, or ``/live_data``. Before a certain version of Socket.IO, there was only a global namespace, and so this behavior was kept for backwards compatibility. Then flows the normal events, back and forth as described elsewhere (elsewhere??). Upon disconnection, here is what happens: [INSERT HERE the details flow of disconnection handling, events fired, physical closing of the connection and ways to terminate a socket, when is the Namespace killed, the state of the spawn'd processes for each Namespace and each virtsocket. This really needs to be done, and I'd appreciate having people investigate this thoroughly] There you go :) Namespace instance properties ----------------------------- .. attribute:: BaseNamespace.session The :term:`session` is a simple ``dict`` that is created with each :class:`~socketio.virtsocket.Socket` instance, and is copied to each Namespace created under it. It is a general purpose store for any data you want to associated with an open :class:`~socketio.virtsocket.Socket`. .. attribute:: BaseNamespace.request This is the ``request`` object (or really, any object) that you have passed as the ``request`` parameter to the :func:`~socketio.socketio_manage` function. .. attribute:: BaseNamespace.ns_name The name of the namespace, like ``/chat`` or the empty string, for the "global" namespace. .. attribute:: BaseNamespace.environ The ``environ`` WSGI dictionary, as it was received upon reception of the **first** request that established the virtual Socket. This will never contain the subsequent ``environ`` for the next polling, so beware when using cookie-based sessions (like Beaker). .. attribute:: BaseNamespace.socket A reference to the :class:`~socketio.virtsocket.Socket` instance this namespace is attached to. Sending data ------------ Functions to send data through the socket: .. automethod:: BaseNamespace.emit .. automethod:: BaseNamespace.send .. automethod:: BaseNamespace.error .. automethod:: BaseNamespace.disconnect Dealing with incoming data -------------------------- .. automethod:: BaseNamespace.recv_connect .. automethod:: BaseNamespace.recv_message .. automethod:: BaseNamespace.recv_json .. automethod:: BaseNamespace.recv_error .. automethod:: BaseNamespace.recv_disconnect .. method:: BaseNamespace.exception_handler_decorator(fn) This method can be a static, class or bound method (that is, with ``@staticmethod``, ``@classmethod`` or without). It receives one single parameter, and that parameter will be the function the framework is trying to call because some information arrived from the remote client, for instance: ``on_*`` and ``recv_*`` functions that you declared on your namespace. The decorator is also used to wrap called to ``self.spawn(self.job_something)``, so that if anything happens after you've spawn'd a greenlet, it will still catch it and handle it. It should return a decorator with exception handling properly dealt with. For example: .. code-block:: python import traceback, sys import logging def exception_handler_decorator(self, fn): def wrap(*args, **kwargs): try: return fn(*args, **kwargs) except Exception, e: stack = traceback.format_exception(*sys.exc_info()) db.Evtrack.write("socketio_exception", {"error": str(e), "trace": stack}, self.request.email) logging.getLogger('exc_logger').exception(e) return wrap .. automethod:: BaseNamespace.process_event You would override this method only if you are not completely satisfied with the automatic dispatching to ``on_``-prefixed methods. You could then implement your own dispatch. See the source code for inspiration. Process management ------------------ Managing the different callbacks, greenlets and tasks you spawn from this namespace: .. automethod:: BaseNamespace.spawn .. automethod:: BaseNamespace.kill_local_jobs ACL system ---------- The ACL system grants access to the different ``on_*()`` and ``recv_*()`` methods of your subclass. Developers will normally override :meth:`get_initial_acl` to return a list of the functions they want to initially open. Usually, it will be an ``on_connect`` event handler, that will perform authentication and/or authorization, set some variables on the Namespace, and then open up the rest of the Namespace using :meth:`lift_acl_restrictions` or more granularly with :meth:`add_acl_method` and :meth:`del_acl_method`. It is also possible to check these things inside :meth:`initialize` when, for example, you have authenticated a Global Namespace object, and you want to re-use those credentials or authentication infos in a new Namespace: .. code-block:: python # GLOBAL_NS = '' class MyNamespace(BaseNamespace): ... def initialize(self): self.my_auth = MyAuthObjet() if self.socket[GLOBAL_NS].my_auth.logged_in == True: self.my_auth.logged_in = True The content of the ACL is a list of strings corresponding to the full name of the methods defined on your subclass, like: ``"on_my_event"`` or ``"recv_json"``. .. automethod:: BaseNamespace.get_initial_acl .. automethod:: BaseNamespace.add_acl_method .. automethod:: BaseNamespace.del_acl_method .. automethod:: BaseNamespace.lift_acl_restrictions .. automethod:: BaseNamespace.reset_acl This function is used internally, but can be useful to the developer: .. automethod:: is_method_allowed This is the attribute where the allowed methods are stored, as a list of strings, or a single ``None``:: .. autoattribute:: allowed_methods Low-level methods ----------------- Packet dispatching methods. These functions are normally not overriden if you are satisfied with the normal dispatch behavior: .. automethod:: BaseNamespace.process_packet .. automethod:: BaseNamespace.call_method_with_acl .. automethod:: BaseNamespace.call_method gevent-socketio-0.3.6/docs/source/server_integration.rst0000644000175000017500000001251512261120545024353 0ustar sonteksontek00000000000000.. _server_integration: Server integration layers ========================= As gevent-socketio runs on top of Gevent, you need a Gevent-based server, to yield the control cooperatively to the Greenlets in there. gunicorn -------- If you have a python file that includes a WSGI application, for gunicorn integration all you have to do is include the :mod:`socketio.sgunicorn` .. code-block:: bash gunicorn --worker-class socketio.sgunicorn.GeventSocketIOWorker module:app paster / Pyramid's pserve ------------------------- Through Gunicorn ^^^^^^^^^^^^^^^^ Gunicorn will handle workers for you and has other features. For paster, you just have to define the configuration like this: .. code-block:: ini [server:main] use = egg:gunicorn#main host = 0.0.0.0 port = 6543 workers = 4 worker_class = socketio.sgunicorn.GeventSocketIOWorker Directly through gevent ^^^^^^^^^^^^^^^^^^^^^^^ Straight gevent integration is the simplest and has no dependencies. In your .ini file: .. code-block:: ini [server:main] use = egg:gevent-socketio#paster host = 0.0.0.0 port = 6543 resource = socket.io transports = websocket, xhr-polling, xhr-multipart policy_server = True policy_listener_host = 0.0.0.0 policy_listener_port = 10843 ``policy_listener_host`` defaults to ``host``, ``policy_listener_port`` defaults to ``10843``, ``transports`` defaults to all transports, ``policy_server`` defaults to ``False`` in here, ``resource`` defaults to ``socket.io``. So you can have a slimmed-down version: .. code-block:: ini [server:main] use = egg:gevent-socketio#paster host = 0.0.0.0 port = 6543 django runserver ---------------- You can either define a wsgi app and launch it with gunicorn: ``wsgi.py``: .. code-block:: python import django.core.handlers.wsgi import os os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' app = django.core.handlers.wsgi.WSGIHandler() from commandline: .. code-block:: bash gunicorn --worker-class socketio.sgunicorn.GeventSocketIOWorker wsgi:app or you can use gevent directly: ``run.py`` .. code-block:: python #!/usr/bin/env python from gevent import monkey from socketio.server import SocketIOServer import django.core.handlers.wsgi import os import sys monkey.patch_all() try: import settings except ImportError: sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) sys.exit(1) PORT = 9000 os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' application = django.core.handlers.wsgi.WSGIHandler() sys.path.insert(0, os.path.join(settings.PROJECT_ROOT, "apps")) if __name__ == '__main__': print 'Listening on http://127.0.0.1:%s and on port 10843 (flash policy server)' % PORT SocketIOServer(('', PORT), application, resource="socket.io").serve_forever() Databases ========= Since gevent is a cooperative concurrency library, no process or routine or library must block on I/O without yielding control to the ``gevent`` hub, if you want your application to be fast and efficient. Making these libraries compatible with such a concurrency model is often called `greening`, in reference to `Green threads `_. You will need `green`_ databases APIs to gevent to work correctly. See: * MySQL: * PyMySQL https://github.com/petehunt/PyMySQL/ * PostgreSQL: * psycopg2 http://initd.org/psycopg/docs/advanced.html#index-8 * psycogreen https://bitbucket.org/dvarrazzo/psycogreen/src Web server front-ends ===================== If your web server does not support websockets, you will not be able to use this transport, although the other transports may work. However, this would diminish the value of using real-time communications. The websocket implementation in the different web servers is getting better every day, but before investing too much too quickly, you might want to have a look at your web server's status on the subject. [INSERT THE STATE OF THE DIFFERENT SERVER IMPLEMENTATIONS SUPPORTING WEBSOCKET FORWARDING] nginx status ---------------- Nginx added the ability to support websockets with version 1.3.13 but it requires a bit of explicit configuration. See: http://nginx.org/en/docs/http/websocket.html Assuming your config is setup to proxy to your gevent server via something like this: .. code-block:: nginx location / { proxy_pass http://127.0.0.1:7000; proxy_redirect off; } You'll just need to add this additional location section. Note in this example we're using ``/socket.io`` as the entry point (you might have to change it) .. code-block:: nginx location /socket.io { proxy_pass http://127.0.0.1:7000/socket.io; proxy_redirect off; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } Make sure you're running the latest version of Nginx (or atleast >= 1.3.13). Older versions don't support websockets, and the client will have to fallback to long polling. Apache Using HAProxy to load-balance gevent-socketio-0.3.6/docs/source/packet.rst0000644000175000017500000001163312261120545021711 0ustar sonteksontek00000000000000.. _packet_module: :mod:`socketio.packet` ====================== The day to day user doesn't need to use this module directly. The packets used internally (that might be exposed if you override the :meth:`~socketio.namespace.BaseNamespace.process_packet` method of your Namespace) are dictionaries, and are different from one message type to another. Internal packet types --------------------- Here is a list of message types available in the Socket.IO protocol: The connect packet ~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python {"type": "connect", "qs": "", "endpoint": "/chat"} The ``qs`` parameter is a query string you can add to the io.connect('/chat?a=b'); calls on the client side. The **message** packet, equivalent to Socket.IO version 0.6's string message: .. code-block:: python {"type": "message", "data": "this is the sent string", "endpoint": "/chat"} {"type": "message", "data": "some message, but please reply", "ack": True, "id": 5, "endpoint": "/chat"} This last message includes a **msg_id**, and asks for an ack, which you can reply to with ``self.ack()``, so that the client-side callback is fired upon reception. The json packet ~~~~~~~~~~~~~~~ The **json** packet is like a message, with no name (unlike events) but with structure JSON data attached. It is automatically decoded by gevent-socketio. .. code-block:: python {"type": "json", "data": {"this": "is a json object"}, "endpoint": "/chat"} {"type": "json", "data": {"this": "is a json object", "please": "reply"}, "ack": True, "id": 5, "endpoint": "/chat"} The same ``ack`` mechanics also apply for the ``json`` packet. The event packet ~~~~~~~~~~~~~~~~ The **event** packet holds a ``name`` and some ``args`` as a list. They are taken as a list on the browser side (you can ``socket.emit("event", many, parameters``) in the browser) and passed in as is. .. code-block:: python {"type": "event", "endpoint": "/chat", "name": "my_event", "args": []} {"type": "event", "endpoint": "/chat", "name": "my_event", "ack": True, "id": 123, "args": [{"my": "object"}, 2, "mystring"]} The same ack semantics apply here as well. [INSERT: mark the difference between when YOU create the packet, and when you receive it, and what you must do with it according to different ack values] The heartbeat packet ~~~~~~~~~~~~~~~~~~~~ The **heartbeat** packet just marks the connection as alive for another amount of time. .. code-block:: python {"type": "heartbeat", "endpoint": ""} This packet is for the global namespace (or empty namespace). Ack mechanics ------------- The client sends a message of the sort: .. code-block:: python {"type": "message", "id": 140, "ack": True, "endpoint": "/tobi", "data": ''} The 'ack' value is 'true', marking that we want an automatic 'ack' when it receives the packet. The Node.js version sends the ack itself, without any server-side code interaction. It dispatches the packet only after sending back an ack, so the ack isn't really a reply. It's just marking the server received it, but not if the event/message/json was properly processed. The automated reply from such a request is: .. code-block:: python {"type": "ack", "ackId": 140, "endpoint": '', "args": []} Where 'ackId' corresponds to the 'id' of the originating message. Upon reception of this 'ack' message, the client then looks in an object if there is a callback function to call associated with this message id (140). If so, runs it, otherwise, drops the packet. There is a second way to ask for an ack, sending a packet like this: .. code-block:: python {"type": "event", "id": 1, "ack": "data", "endpoint": '', "name": 'tobi', "args": []} {"type": "json", "id": 1, "ack": "data", "endpoint": '', "data": {"a": "b"}} and the same goes for a 'message' packet, which has the 'ack' equal to 'data'. When the server receives such a packet, it dispatches the corresponding event (either the named event specified in an 'event' type packet, or 'message' or 'json, if the type is so), and *adds* as a parameter, in addition to the 'args' passed by the event (or 'data' for 'message'/'json'), the ack() function to call (it encloses the packet 'id' already). Any number of arguments passed to that 'ack()' function will be passed on to the client-side, and given as parameter on the client-side function. That is the returning 'ack' message, with the data ready to be passed as arguments to the saved callback on the client side: .. code-block:: python {"type": "ack", "ackId": 12, "endpoint": '', "args": ['woot', 'wa']} To learn more, see the `test_packet.py `_ test cases. It also shows the serialization that happens on the wire. Other module members -------------------- .. automodule:: socketio.packet :members: :undoc-members: :show-inheritance: gevent-socketio-0.3.6/docs/source/server.rst0000644000175000017500000000056212261120545021747 0ustar sonteksontek00000000000000.. _server_module: :mod:`socketio.server` ====================== This is the component used to hook Gevent and its WSGI server to the WSGI app to be served, while dispatching any Socket.IO related activities to the `handler` and the `transports`. .. automodule:: socketio.server :members: :undoc-members: :show-inheritance: :special-members: __init__ gevent-socketio-0.3.6/docs/source/mixins.rst0000644000175000017500000000036512261120545021751 0ustar sonteksontek00000000000000.. _mixins_module: :mod:`socketio.mixins` ====================== .. automodule:: socketio.mixins .. literalinclude:: ../../socketio/mixins.py :pyobject: BroadcastMixin .. literalinclude:: ../../socketio/mixins.py :pyobject: RoomsMixin gevent-socketio-0.3.6/docs/source/conf.py0000644000175000017500000002011112273550306021203 0ustar sonteksontek00000000000000# -*- coding: utf-8 -*- # # gevent-socketio documentation build configuration file, created by # sphinx-quickstart on Tue Mar 13 20:43:40 2012. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os import datetime # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. here = os.path.dirname(__file__) socketio_path = os.path.abspath(os.path.join(here, '../..')) sys.path.insert(0, socketio_path) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'gevent-socketio' copyright = '2011-%s, Jeffrey Gelens, Alexandre Bourget, and John Anderson' % datetime.datetime.now().year # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.3.1' # The full version, including alpha/beta/rc tags. release = '0.3.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'gevent-socketiodoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'gevent-socketio.tex', u'gevent-socketio Documentation', u'Jeffrey Gelens,Alex Bourget,John Anderson', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'gevent-socketio', u'gevent-socketio Documentation', [u'Jeffrey Gelens,Alex Bourget,John Anderson'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'gevent-socketio', u'gevent-socketio Documentation', u'Jeffrey Gelens,Alex Bourget,John Anderson', 'gevent-socketio', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} gevent-socketio-0.3.6/docs/source/virtsocket.rst0000644000175000017500000000025612273550306022643 0ustar sonteksontek00000000000000.. _virtsocket_module: :mod:`socketio.virtsocket` ========================== .. automodule:: socketio.virtsocket :members: :undoc-members: :show-inheritance: gevent-socketio-0.3.6/README.rst0000644000175000017500000000364512261120545017153 0ustar sonteksontek00000000000000Presentation ============ .. image:: https://secure.travis-ci.org/abourget/gevent-socketio.png?branch=master ``gevent-socketio`` is a Python implementation of the Socket.IO protocol, developed originally for Node.js by LearnBoost and then ported to other languages. Socket.IO enables real-time web communications between a browser and a server, using a WebSocket-like API. One aim of this project is to provide a single ``gevent``-based API that works across the different WSGI-based web frameworks out there (Pyramid, Pylons, Flask, web2py, Django, etc...). Only ~3 lines of code are required to tie-in ``gevent-socketio`` in your framework. Note: you need to use the ``gevent`` python WSGI server to use ``gevent-socketio``. Technical overview ================== Most of the ``gevent-socketio`` implementation is pure Python. There is an obvious dependency on ``gevent``, and another on ``gevent-websocket``. There are integration examples for Pyramid, Flask, Django and BYOF (bring your own framework!). Documentation and References ============================ You can read the renderered Sphinx docs at: * http://readthedocs.org/docs/gevent-socketio/en/latest/ Discussion and questions happen on the mailing list: * https://groups.google.com/forum/#!forum/gevent-socketio or in the Github issue tracking: * https://github.com/abourget/gevent-socketio/issues You can also contact the maintainer: * https://twitter.com/#!/bourgetalexndre * https://plus.google.com/109333785244622657612 Installation ============ You can install with standard Python methods:: pip install gevent-socketio or from source:: git clone git://github.com/abourget/gevent-socketio.git cd gevent-socketio python setup.py install For development, run instead of ``install``:: python setup.py develop If you want to do all of that in a virtualenv, run:: virtualenv env . env/bin/activate python setup.py develop # or install gevent-socketio-0.3.6/tests/0000755000175000017500000000000012273622372016626 5ustar sonteksontek00000000000000gevent-socketio-0.3.6/tests/test_packet.py0000644000175000017500000003573712261120545021515 0ustar sonteksontek00000000000000""" Tests based on the Socket.IO spec: https://github.com/LearnBoost/socket.io-spec """ from unittest import TestCase, main from socketio.packet import encode, decode import decimal class TestEncodeMessage(TestCase): def test_encode_disconnect(self): """encoding a disconnection packet """ encoded_message = encode({'type': 'disconnect', 'endpoint': '/woot' }) self.assertEqual(encoded_message, '0::/woot') def test_encode_connect(self): """encoding a connection packet """ encoded_message = encode({'type': 'connect', 'endpoint': '/tobi', 'qs': '', }) self.assertEqual(encoded_message, '1::/tobi') # encoding a connection packet with query string encoded_message = encode({'type': 'connect', 'endpoint': '/test', 'qs': '?test=1' }) self.assertEqual(encoded_message, '1::/test:?test=1') def test_encode_heartbeat(self): """encoding a connection packet """ encoded_message = encode({'type': 'heartbeat', 'endpoint': '' }) self.assertEqual(encoded_message, '2::') def test_encode_message(self): """encoding a message packet """ encoded_message = encode({'type': 'message', 'endpoint': '', 'data': 'woot' }) self.assertEqual(encoded_message, '3:::woot') # encoding a message packet with id and endpoint encoded_message = encode({'type': 'message', 'endpoint': '/tobi', 'id': 5, 'ack': True, 'data': '' }) self.assertEqual(encoded_message, '3:5:/tobi') def test_encode_json(self): """encoding JSON packet """ encoded_message = encode({'type': 'json', 'endpoint': '', 'data': '2' }) self.assertEqual(encoded_message, '4:::"2"') # encoding json packet with message id and ack data encoded_message = encode({'type': 'json', 'id': 1, 'ack': 'data', 'endpoint': '', 'data': {'a' : 'b'} }) self.assertEqual(encoded_message, '4:1+::{"a":"b"}') def test_encode_json_decimals(self): """encoding JSON packet with a decimal""" # encoding json packet with message id and ack data encoded_message = encode({'type': 'json', 'id': 1, 'ack': 'data', 'endpoint': '', 'data': {'a' : decimal.Decimal('%0.1f' % (0.5))} }) self.assertEqual(encoded_message, '4:1+::{"a":0.5}') def test_encode_event(self): """encoding an event packet """ encoded_message = encode({'type': 'event', 'endpoint': '', 'name': 'woot', 'args': [] }) self.assertEqual(encoded_message, '5:::{"name":"woot"}') # encoding an event packet with message id and ack encoded_message = encode({'type': 'event', 'name': 'tobi', 'id': 1, 'ack': True, 'data': '' }) self.assertEqual(encoded_message, '5:1::{"name":"tobi"}') # encoding an event packet with message id and ack = 'data' encoded_message = encode({'type': 'event', 'name': 'tobi', 'id': 1, 'ack': 'data', 'data': '' }) self.assertEqual(encoded_message, '5:1+::{"name":"tobi"}') # encoding an event packet with data encoded_message = encode({'type': 'event', 'name': 'edwald', 'ack': True, 'endpoint': '', 'args': [{"a":"b"}, 2,"3"] }) self.assertEqual(encoded_message, '5:::{"args":[{"a":"b"},2,"3"],"name":"edwald"}') def test_encode_ack(self): """encoding ack packet """ encoded_message = encode({'type': 'ack', 'ackId': 140, 'endpoint': '', 'args': [] }) self.assertEqual(encoded_message, '6:::140') # encoding ack packet with args encoded_message = encode({'type': 'ack', 'ackId': 12, 'endpoint': '', 'args': ["woot","wa"] }) self.assertEqual(encoded_message, '6:::12+["woot","wa"]') # encoding ack packet with args and endpoint encoded_message = encode({'type': 'ack', 'ackId': 12, 'endpoint': '/chat', 'args': ["woot","wa"] }) self.assertEqual(encoded_message, '6::/chat:12+["woot","wa"]') def test_encode_error(self): """encoding error packet """ encoded_message = encode({'type': 'error', 'reason': '', 'advice': '', 'endpoint': '' }) self.assertEqual(encoded_message, '7:::') # encoding error packet with reason encoded_message = encode({'type': 'error', 'reason': 'transport not supported', 'advice': '', 'endpoint': '' }) self.assertEqual(encoded_message, '7:::0') # encoding error packet with reason and advice encoded_message = encode({'type': 'error', 'reason': 'unauthorized', 'advice': 'reconnect', 'endpoint': '' }) self.assertEqual(encoded_message, '7:::2+0') # encoding error packet with endpoint encoded_message = encode({'type': 'error', 'reason': '', 'advice': '', 'endpoint': '/woot' }) self.assertEqual(encoded_message, '7:::/woot') def test_encode_noop(self): """encoding a noop packet """ encoded_message = encode({'type': 'noop', 'endpoint': '', 'data': '' }) self.assertEqual(encoded_message, '8::') class TestDecodeMessage(TestCase): def test_decode_deconnect(self): """decoding a disconnection packet """ decoded_message = decode('0::/woot') self.assertEqual(decoded_message, {'type': 'disconnect', 'endpoint': '/woot' }) def test_decode_connect(self): """decoding a connection packet """ decoded_message = decode('1::/tobi') self.assertEqual(decoded_message, {'type': 'connect', 'endpoint': '/tobi', 'qs': '' }) # decoding a connection packet with query string decoded_message = decode('1::/test:?test=1') self.assertEqual(decoded_message, {'type': 'connect', 'endpoint': '/test', 'qs': '?test=1' }) def test_decode_heartbeat(self): """decoding a heartbeat packet """ decoded_message = decode('2:::') self.assertEqual(decoded_message, {'type': 'heartbeat', 'endpoint': '' }) def test_decode_message(self): """decoding a message packet """ decoded_message = decode('3:::woot') self.assertEqual(decoded_message, {'type': 'message', 'endpoint': '', 'data': 'woot'}) # decoding a message packet with id and endpoint decoded_message = decode('3:5:/tobi') self.assertEqual(decoded_message, {'type': 'message', 'id': 5, 'ack': True, 'endpoint': '/tobi', 'data': ''}) def test_decode_json(self): """decoding json packet """ decoded_message = decode('4:::"2"') self.assertEqual(decoded_message, {'type': 'json', 'endpoint': '', 'data': '2'}) # decoding json packet with message id and ack data decoded_message = decode('4:1+::{"a":"b"}') self.assertEqual(decoded_message, {'type': 'json', 'id': 1, 'endpoint': '', 'ack': 'data', 'data': {u'a': u'b'}}) def test_decode_event(self): """decoding an event packet """ decoded_message = decode('5:::{"name":"woot", "args": ["foo"]}') self.assertEqual(decoded_message, {'type': 'event', 'name': 'woot', 'endpoint': '', 'args': ["foo"]}) decoded_message = decode('5:::{"name":"woot"}') self.assertEqual(decoded_message, {'type': 'event', 'name': 'woot', 'endpoint': '', 'args': []}) # decoding an event packet with message id and ack decoded_message = decode('5:1+::{"name":"tobi"}') self.assertEqual(decoded_message, {'type': 'event', 'id': 1, 'ack': 'data', 'name': 'tobi', 'endpoint': '', 'args': []}) def test_decode_event_error(self): """decoding an event packet """ decoded_message = decode('5:::') self.assertEqual(decoded_message, {'args': [], 'type': 'event', 'endpoint': '', }) def test_decode_ack(self): """decoding a ack packet """ decoded_message = decode('6:::140') self.assertEqual(decoded_message, {'type': 'ack', 'ackId': 140, 'endpoint': '', 'args': []}) # Decode with endpoint decoded_message = decode('6::/chat:140') self.assertEqual(decoded_message, {'type': 'ack', 'ackId': 140, 'endpoint': '/chat', 'args': []}) # With args decoded_message = decode('6::/chat:140+["bob", "bob2"]') self.assertEqual(decoded_message, {'type': 'ack', 'ackId': 140, 'endpoint': '/chat', 'args': [u"bob", u"bob2"]}) def test_decode_error(self): """decoding error packet """ decoded_message = decode('7:::') self.assertEqual(decoded_message, {'type': 'error', 'reason': '', 'advice': '', 'endpoint': ''}) decoded_message = decode('7:::0') self.assertEqual(decoded_message, {'type': 'error', 'reason': 'transport not supported', 'advice': '', 'endpoint': ''}) # decoding error packet with reason and advice decoded_message = decode('7:::2+0') self.assertEqual(decoded_message, {'type': 'error', 'reason': 'unauthorized', 'advice': 'reconnect', 'endpoint': ''}) # decoding error packet with endpoint decoded_message = decode('7::/woot') self.assertEqual(decoded_message, {'type': 'error', 'reason': '', 'advice': '', 'endpoint': '/woot'}) def test_decode_new_line(self): """test decoding newline """ decoded_message = decode('3:::\n') self.assertEqual(decoded_message, {'type': 'message', 'data': '\n', 'endpoint': ''}) def test_decode_noop(self): """decoding a noop packet """ decoded_message = decode('8::') self.assertEqual(decoded_message, {'type': 'noop', 'endpoint': '' }) def test_except_on_invalid_message_type(self): """decoding a noop packet """ try: decoded_message = decode('99::') except Exception as e: self.assertEqual(e.message, "Unknown message type: 99") else: self.assertEqual(decoded_message, None, "We should not get a valid message") if __name__ == '__main__': main() gevent-socketio-0.3.6/tests/test_namespace.py0000644000175000017500000002542212261120545022170 0ustar sonteksontek00000000000000from unittest import TestCase, main from socketio.namespace import BaseNamespace from socketio.virtsocket import Socket from mock import MagicMock class MockSocketIOServer(object): """Mock a SocketIO server""" def __init__(self, *args, **kwargs): self.sockets = {} def get_socket(self, socket_id=''): return self.sockets.get(socket_id) class MockSocket(Socket): pass class ChatNamespace(BaseNamespace): def __init__(self, *args, **kwargs): self.use_set = args[0] super(ChatNamespace, self).__init__(*args[1:], **kwargs) def get_initial_acl(self): acls = ['on_foo'] if self.use_set == True: return set(acls) else: return acls def on_foo(self): return 'a' def on_bar(self): return 'b' def on_baz(foo, bar, baz): return 'c' class GlobalNamespace(BaseNamespace): def on_woot(self): return '' def on_tobi(self): return '' class TestBaseNamespace(TestCase): def setUp(self): server = MockSocketIOServer() self.environ = {} socket = MockSocket(server, {}) socket.error = MagicMock() self.environ['socketio'] = socket self.ns = GlobalNamespace(self.environ, '/woot') def test_process_packet_disconnect(self): pkt = {'type': 'disconnect', 'endpoint': '/woot' } self.ns.process_packet(pkt) assert not self.environ['socketio'].error.called def test_process_packet_connect(self): """processing a connection packet """ pkt = {'type': 'connect', 'endpoint': '/tobi', 'qs': '' } self.ns.process_packet(pkt) assert not self.environ['socketio'].error.called # processing a connection packet with query string pkt = {'type': 'connect', 'endpoint': '/test', 'qs': '?test=1' } self.ns.process_packet(pkt) assert not self.environ['socketio'].error.called def test_process_packet_heartbeat(self): """processing a heartbeat packet """ pkt = {'type': 'heartbeat', 'endpoint': '' } self.ns.process_packet(pkt) assert not self.environ['socketio'].error.called def test_process_packet_message(self): """processing a message packet """ pkt = {'type': 'message', 'endpoint': '', 'data': 'woot'} data = self.ns.process_packet(pkt) self.assertEqual(data, pkt['data']) assert not self.environ['socketio'].error.called # processing a message packet with id and endpoint pkt = {'type': 'message', 'id': 5, 'ack': True, 'endpoint': '/tobi', 'data': ''} data = self.ns.process_packet(pkt) self.assertEqual(data, pkt['data']) assert not self.environ['socketio'].error.called def test_process_packet_json(self): """processing json packet """ pkt = {'type': 'json', 'endpoint': '', 'data': '2'} data = self.ns.process_packet(pkt) self.assertEqual(data, pkt['data']) assert not self.environ['socketio'].error.called # processing json packet with message id and ack data pkt = {'type': 'json', 'id': 1, 'endpoint': '', 'ack': 'data', 'data': {u'a': u'b'}} data = self.ns.process_packet(pkt) self.assertEqual(data, pkt['data']) assert not self.environ['socketio'].error.called def test_process_packet_event(self): """processing an event packet """ pkt = {'type': 'event', 'name': 'woot', 'endpoint': '', 'args': []} self.ns.process_packet(pkt) assert not self.environ['socketio'].error.called # processing an event packet with message id and ack pkt = {'type': 'event', 'id': 1, 'ack': 'data', 'name': 'tobi', 'endpoint': '', 'args': []} self.ns.process_packet(pkt) assert not self.environ['socketio'].error.called def test_process_packet_ack(self): """processing a ack packet """ pkt = {'type': 'ack', 'ackId': 140, 'endpoint': '', 'args': []} self.ns.process_packet(pkt) assert not self.environ['socketio'].error.called def test_process_packet_error(self): """processing error packet """ pkt = {'type': 'error', 'reason': '', 'advice': '', 'endpoint': ''} self.ns.process_packet(pkt) pkt = {'type': 'error', 'reason': 'transport not supported', 'advice': '', 'endpoint': ''} self.ns.process_packet(pkt) # processing error packet with reason and advice pkt = {'type': 'error', 'reason': 'unauthorized', 'advice': 'reconnect', 'endpoint': ''} self.ns.process_packet(pkt) # processing error packet with endpoint pkt = {'type': 'error', 'reason': '', 'advice': '', 'endpoint': '/woot'} self.ns.process_packet(pkt) def test_process_packet_message_with_new_line(self): """processing a newline in a message""" pkt = {'type': 'message', 'data': '\n', 'endpoint': ''} self.ns.process_packet(pkt) assert not self.environ['socketio'].error.called def test_del_acl_method(self): pkt = {'type': 'event', 'name': 'foo', 'endpoint': '/chat', 'args': []} message = ("Trying to delete an ACL method, but none were" + " defined yet! Or: No ACL restrictions yet, why would you" + " delete one?") try: self.ns.del_acl_method('on_foo') self.ns.process_packet(pkt) except ValueError as e: self.assertEqual( message, e.message, ) else: raise Exception("""We should not be able to delete an acl that doesn't exist""") def test_allowed_event_name_regex(self): pkt = {'type': 'event', 'name': '$foo', 'endpoint': '/chat', 'args': []} self.ns.process_packet(pkt) args = ['unallowed_event_name', 'name must only contains alpha numerical characters', ] kwargs = dict(msg_id=None, endpoint='/woot', quiet=False) self.environ['socketio'].error.assert_called_with(*args, **kwargs) def test_method_not_found(self): """ test calling a method that doesn't exist """ pkt = {'type': 'event', 'name': 'foo', 'endpoint': '/chat', 'args': [] } self.ns.process_packet(pkt) kwargs = dict( msg_id=None, endpoint='/woot', quiet=False ) self.environ['socketio'].error.assert_called_with( 'no_such_method', 'The method "%s" was not found' % 'on_foo', **kwargs ) class TestChatNamespace(TestCase): def setUp(self): server = MockSocketIOServer() self.environ = {} socket = MockSocket(server, {}) socket.error = MagicMock() self.environ['socketio'] = socket self.ns = ChatNamespace( False, self.environ, '/chat' ) def test_allowed_event(self): pkt = {'type': 'event', 'name': 'foo', 'endpoint': '/chat', 'args': []} self.ns.process_packet(pkt) assert not self.environ['socketio'].error.called def test_blocked_event(self): pkt = {'type': 'event', 'name': 'bar', 'endpoint': '/chat', 'args': []} self.ns.process_packet(pkt) args = [ 'method_access_denied', 'You do not have access to method "on_bar"', ] kwargs = dict( msg_id=None, endpoint='/chat', quiet=False ) self.environ['socketio'].error.assert_called_with(*args, **kwargs) def test_add_acl_method(self): pkt = {'type': 'event', 'name': 'bar', 'endpoint': '/chat', 'args': []} self.ns.add_acl_method('on_bar') self.ns.process_packet(pkt) assert not self.environ['socketio'].error.called def test_del_acl_method(self): pkt = {'type': 'event', 'name': 'foo', 'endpoint': '/chat', 'args': []} self.ns.del_acl_method('on_foo') self.ns.process_packet(pkt) args = [ 'method_access_denied', 'You do not have access to method "on_foo"', ] kwargs = dict( msg_id=None, endpoint='/chat', quiet=False ) self.environ['socketio'].error.assert_called_with(*args, **kwargs) def test_lift_acl_restrictions(self): pkt1 = {'type': 'event', 'name': 'foo', 'endpoint': '/chat', 'args': []} self.ns.lift_acl_restrictions() self.ns.process_packet(pkt1) assert not self.environ['socketio'].error.called pkt2 = {'type': 'event', 'name': 'bar', 'endpoint': '/chat', 'args': []} self.ns.process_packet(pkt2) assert not self.environ['socketio'].error.called def test_use_set_on_acl(self): self.ns = ChatNamespace( True, self.environ, '/chat' ) pkt = {'type': 'event', 'name': 'bar', 'endpoint': '/chat', 'args': []} self.ns.add_acl_method('on_bar') self.ns.process_packet(pkt) assert not self.environ['socketio'].error.called def test_call_method_invalid_definition(self): pkt = {'type': 'event', 'name': 'baz', 'endpoint': '/chat', 'args': []} self.ns.add_acl_method('on_baz') self.ns.process_packet(pkt) kwargs = dict(msg_id=None, endpoint='/chat', quiet=False) self.environ['socketio'].error.assert_called_with( "invalid_method_args", "The server-side method is invalid, as it doesn't " "have 'self' as its first argument" , **kwargs) if __name__ == '__main__': main() gevent-socketio-0.3.6/tests/__init__.py0000644000175000017500000000000012261120545020715 0ustar sonteksontek00000000000000gevent-socketio-0.3.6/tests/test_socket.py0000644000175000017500000000540012261120545021516 0ustar sonteksontek00000000000000from unittest import TestCase, main from socketio.namespace import BaseNamespace from socketio.virtsocket import Socket class MockSocketIOServer(object): """Mock a SocketIO server""" def __init__(self, *args, **kwargs): self.sockets = {} def get_socket(self, socket_id=''): return self.sockets.get(socket_id) class MockSocketIOhandler(object): """Mock a SocketIO handler""" def __init__(self, *args, **kwargs): self.server = MockSocketIOServer() class MockNamespace(BaseNamespace): """Mock a Namespace from the namespace module""" pass class TestSocketAPI(TestCase): """Test the virtual Socket object""" def setUp(self): self.server = MockSocketIOServer() self.virtsocket = Socket(self.server, {}) def test__set_namespaces(self): namespaces = {'/': MockNamespace} self.virtsocket._set_namespaces(namespaces) self.assertEqual(self.virtsocket.namespaces, namespaces) def test__set_request(self): request = {'test': 'a'} self.virtsocket._set_request(request) self.assertEqual(self.virtsocket.request, request) def test__set_environ(self): environ = [] self.virtsocket._set_environ(environ) self.assertEqual(self.virtsocket.environ, environ) def test_connected_property(self): # not connected self.assertFalse(self.virtsocket.connected) # connected self.virtsocket.state = "CONNECTED" self.assertTrue(self.virtsocket.connected) def test_incr_hist(self): self.virtsocket.state = "CONNECTED" # cause a hit self.virtsocket.incr_hits() self.assertEqual(self.virtsocket.hits, 1) self.assertEqual(self.virtsocket.state, self.virtsocket.STATE_CONNECTED) def test_disconnect(self): # kill connected socket self.virtsocket.state = "CONNECTED" self.virtsocket.active_ns = {'test' : MockNamespace({'socketio': self.virtsocket}, 'test')} self.virtsocket.disconnect() self.assertEqual(self.virtsocket.state, "DISCONNECTING") self.assertEqual(self.virtsocket.active_ns, {}) def test_kill(self): # kill connected socket self.virtsocket.state = "CONNECTED" self.virtsocket.active_ns = {'test' : MockNamespace({'socketio': self.virtsocket}, 'test')} self.virtsocket.kill() self.assertEqual(self.virtsocket.state, "DISCONNECTING") def test__receiver_loop(self): """Test the loop """ # most of the method is tested by test_packet.TestDecode and # test_namespace.TestBaseNamespace pass # self.virtsocket._receiver_loop() # self.virtsocket.server_queue.put_nowait_msg('2::') if __name__ == '__main__': main() gevent-socketio-0.3.6/tests/jstests/0000755000175000017500000000000012273622372020325 5ustar sonteksontek00000000000000gevent-socketio-0.3.6/tests/jstests/jstests.py0000755000175000017500000000426712273562633022415 0ustar sonteksontek00000000000000from gevent import monkey; monkey.patch_all() from socketio import socketio_manage from socketio.server import SocketIOServer from socketio.namespace import BaseNamespace TestHtml = """ Gevent-socketio Tests
""" class TestNamespace(BaseNamespace): def on_requestack(self, val): return val, "ack" def on_requestackonevalue(self, val): return val class Application(object): def __init__(self): self.buffer = [] def __call__(self, environ, start_response): path = environ['PATH_INFO'].strip('/') if not path: start_response('200 OK', [('Content-Type', 'text/html')]) return [TestHtml] if path.startswith('static/') or path.startswith('tests/'): try: data = open(path).read() except Exception: return not_found(start_response) if path.endswith(".js"): content_type = "text/javascript" elif path.endswith(".css"): content_type = "text/css" elif path.endswith(".swf"): content_type = "application/x-shockwave-flash" else: content_type = "text/html" start_response('200 OK', [('Content-Type', content_type)]) return [data] if path.startswith("socket.io"): socketio_manage(environ, {'/test': TestNamespace}) else: return not_found(start_response) def not_found(start_response): start_response('404 Not Found', []) return ['

Not Found

'] if __name__ == '__main__': print 'Listening on port 8080 and on port 10843 (flash policy server)' SocketIOServer(('0.0.0.0', 8080), Application(), resource="socket.io", policy_server=True).serve_forever() gevent-socketio-0.3.6/tests/jstests/tests/0000755000175000017500000000000012273622372021467 5ustar sonteksontek00000000000000gevent-socketio-0.3.6/tests/jstests/tests/suite.js0000644000175000017500000000321712273562633023164 0ustar sonteksontek00000000000000testTransport = function(transports) { var prefix = "socketio - " + transports + ": "; connect = function(transports) { // Force transport io.transports = transports; deepEqual(io.transports, transports, "Force transports"); var options = { 'force new connection': true } return io.connect('/test', options); } asyncTest(prefix + "Connect", function() { expect(4); test = connect(transports); test.on('connect', function () { ok( true, "Connected with transport: " + test.socket.transport.name ); test.disconnect(); }); test.on('disconnect', function (reason) { ok( true, "Disconnected - " + reason ); test.socket.disconnect(); start(); }); test.on('connect_failed', function () { ok( false, "Connection failed"); start(); }); }); asyncTest(prefix + "Emit with ack", function() { expect(3); test = connect(transports); test.emit('requestack', 1, function (val1, val2) { equal(val1, 1); equal(val2, "ack"); test.disconnect(); test.socket.disconnect(); start(); }); }); asyncTest(prefix + "Emit with ack one return value", function() { expect(2); test = connect(transports); test.emit('requestackonevalue', 1, function (val1) { equal(val1, 1); test.disconnect(); test.socket.disconnect(); start(); }); }); } transports = [io.transports]; // Validate individual transports for(t in io.transports) { if(io.Transport[io.transports[t]].check()) { transports.push([io.transports[t]]); } } for(t in transports) { testTransport(transports[t]) } gevent-socketio-0.3.6/tests/jstests/static/0000755000175000017500000000000012273622372021614 5ustar sonteksontek00000000000000gevent-socketio-0.3.6/tests/jstests/static/WebSocketMain.swf0000644000175000017500000053173212261120545025033 0ustar sonteksontek00000000000000CWS ȃxڴ `S?mh 6֤mf,mfi|l,AdS}}"(저l"$MKA}M,gfΜ9s̽ ?NH 7!!61:\q{(̓`WYߑ!,((eDŀ@7hn <Ё X._Ey8oP3O>fu Х͚AI 2BԑZhsYvY͸'e v9|: nYFYHM6D{{I0giƭӭ'l!+ɎMp 8I!o Cb42\6{!|VXx4gEBaIz?r} s¯ˈZg|z#3YK掻wכwoL‘1{e53&qOM>S{O5o|G޸՛0Ͻ>tsg'[OzxmK[rWճZ?ta<>n+1|7I<|Cܖ񮼱{=s[+_#wܺ-p^X^ؼNˍ.yq諟c~6;'aZp)w۹]~{I N_][ʅ?.^r.{OVt?WO5?5s]f<{c2V ?==c޺I-t½c~5o^Αgmf?x%3\d|D/{7p_~gE}i+ZNvy㷣 Zl{ӝGX_X}yoa"gaX>똟m= w'_a}9vك5~2"U2[nm]{U/Ģ +cc!ʬ#ݳ:O'gae-Ytft82b#/~p{fg<15㿽fd\z>撃dle)睅_|<)o4f%Co['pOxk{71fOhۯ}ڏݛV?{Kw> =delQִCq|}wap&z?xK[ʹybhS^ x-cvl%d_':Țvexւ0{ԅg{5؏ڎ1z`BEnřqӋ~nLsh;[yG~4;jឱw/1s#aldD7vM˜hUOw=mWy{՝v]ƙͥk/g{4sE:Xg7hwQߍy3E#ֵl~p>ά#g3έ5sop?~T;~~F _ 7|Os O^tj!֮oq~Z1f]7ky{W`;݌mK'rf>w˱tsǬf35cm30?߰l>{s#.~Ӭ[۴*S~}}#}K_h8=]K3gƘruie;^mO~߂Zs|G֬e^1v :;ejI#W0?q7[a%˯|}ʦ+<}[F/a^lWr?eK[p^4f8sir~ۼ7ۮpg#G~hgǝx͕cGg\2i9?O6OG;3ٶtN wѿ3֞7{{kYk04Ə?8E7/Nl{˘k.kU/ x5skk[&ǘ|x7}=~eddOhdھc{OF}9kw߭+fr^94wpyKk#&y&q\t -Y"yF0^?m4csy-^}ZGdgƔkGw1nzSc1藜,ݲ|C3gf$j|[2u4w΂kGxMƑG&2/{}*կ߻5>_of͸(e[ň݌%/`csO)#|ws֘_-嶵4c16֤Lݺg7n7\|mOz9ܳWMXbIk]9%۸'{؉69rاUO~Y{y=olm֏n~q䏻9[~sㇵ+[YÜ׼]_۹Y֎xpšqi_s5iߍ{"c3g-:7:a-sۼ_c\u_N~U+oro{|dŁ>n]hg[1X8~YSǛl4IK[y6eÎ_?c:3߸C ī[^\f(?dm64fNƜ'~uyɯ[ɜsu|m?8ֺi$cG3V9|}tq5'geq)|?_f?|3}Yg{gNj]{=cx:95橭p/tSֈFVX?2ڮ:ou+WV]?v??|779{]͌#V-?6z79j0[srg~p|&7a8'rOA)<nogs9 n:͓GSMÙp/0]ۯ-Ϣ琵c~N^PM_}4"uEρ_'8N uo. x l8Wt| Zqm;_֜,̟ 3t+$fK]?_ ~>Qm+SqoT`l*eA{o~ z^5653{Lm`=n7dJke?_ݐl\!T&6d'yv,E4@шry1Oo}*7|ZGr,Q |[{>7c.boCsZa~}3 ͠ϫCq0CY`߀uӫ_ߒFsx> ̇@™П}xϳ9oSgࠕo&<L >eX. Y} i{wwg[&y3 yJ?ٵk@n>R>] Z.yM9)mo'0y@,cg|~aOc_~=3鱯A?7m]<%Ur.|y z( l;X @= ;po;} Ga-s>}t> 9=1 za`׻w~ zqϖ)xե9rs> 쩈7'! _: xb{p ɐ.'80eF|N cMӵ=ނ(X 8~oyw"1/a>s#`L? Ձ\vs`A 7<&:υcyںۇm/7y0=Akye+dKW=\Uz$̻ /k+ }Agby |p?r>t0%cV~~ < pP#/{kз;O^;Mpt1r)U@f. =_LP}~cA 'F\SGR<n5(obyЇ3-W_gIAD#UB| H9Tk ~ ߳Cs?v\(y~ z0ӌ^J:N } rȿdѣ>;X⸌q@\8rُ.Nxe߹Cw5~ 'T7m: hf5{5L.|]97Яg>cUz6كO~1 vg`Y^X)i=~!n.<2ariGyqbFPU~6a1!p `ύGOy@S]ZOx1m_S>9Xˬ<'<nW9An]m>+- \jĔ1K΃Ak12+Vb4 i,> @&;j#ZчW#a&D']UO>`]' iy7'^e0=Q\ KO.G|hcx?4?'r@~ 7!J7|eXУR{x\ᇠ7~wț3<Ǽ*oa=~(3{3wes *{\CzWOz:/^/=\trv >w=Zz!ʒ~<qᐻ*V^/f_^ }2_z {m%X<6IɃo,>W;* ~nkm% m^q+wC3Ir~)E{?5ŀC_M|pЏq'}qD_oVc4> G$_% '4Xy=aݪ ~1k`G@+ \tUA^ oO8 v=q7WzȎ^}ʌ~3 c7d#ςħC`F֌~_88<g7v#y6;88Ǟ@5'> 4[`n x:#g嵷@o5pwz#cnԩ`WCe;@a<+{Ϳsa{6߈`b~ww?|p};'mm9\G Y¾EOn>;*Kʃ'݂v=qA)M\{{`:-Ez[o)d7ry77&+$. pB*cmJ)snNVjd~?^z򻂍E)Z?.w I$avv[ )s;C~L}A*0suzlZ+,ˎ!%ۍ:sQDy H(6%rRsЬBnԄ(`\uc!SOgKϕ_Jm_SyA'_r(Aɹ9^Wg(Os~}-p+Z'Puܵt]=8uG:w;)^t4$% >DLz-Gll(ULQ꣣%(D=Y"V,4JiFOtU =GB\)#htLF~7FZB=foRnQrV6~8() !29@%(#]q&RnƥN}f-'X$;@zm )~J@ j.b$U"S(o15jo I/b2/?EeRR_]](GtGt{^@lQ%X bJ O4p{RHkdPճv9Ns-LKyI8K"${"K,'=T=)I;'9H/G"sMƢ1J`B0Ʊ|ZSe"t+,R2 (@5d fd|qHa$J(dT w  ;qB=Q;_w2N/h ~9u$$!f ǫG 0E+SuO"ERtr(_G/c]vI¥ djtJ쭏D'mXW0i1 W<+fI1KAFkՑv!܊*3tO]#y=G+E繡XBAxFJV3z4$DF[fVb6rV-ȷdC5zW=UhH.%r\쉓P""pi8@rI )KDR#֣ZMJzt2D4#pguwvw!ed%{}*ӏ(uݡ3ZL6D|kR!X&7c SZ=d^:&HE\+)ezPa(hAZTz| Z2957+ґVo $\I'D ,Z&*&No{4FO6iĵte7pD6[dgxT7>4G1aF!;Tj{b3vxk9mFBѹO,$f hA7Dh/ֺ43lBҠ;x޻$Nf4G=@A: netݷc==UK~\>)ѽCdiȷU W4E}lj[2'./`KlzClP=]e08Ӳ ")q(HtJ;;6N?CڎOjR:"p>rtb7MБNv^|aBtlhWQ2{{(d";3b[0,9)6._ݡh K)$D\D8҈lhv$b``_""W\Q"$dnm#҈h"A a6qRV2) \H ;QP 7. S/@}C^$N]Dg0T$ib`/1H4cn8H&`^(褚hɖ,,KhuCFAmxc^&QMmj&C$>=*vu_ȸsB܉A2 Ǩc<>!S'X)n[^) pRF[f2Nq܁C*f!E̖gx!cH4E*MD_5 m|H(E{_HDȳm!/Fi/+%͎()d!aiN!x6ĥCJ63I4u|T< MQDN>XdXXpz'=,?fڃ`r n4ކ&Dv" eRsV+@a'Rz$KT/ڴ:C9+1Bf@4 f'dDLi%:Qor 0K6ahe=HL@ LRIsP]5҄exv;`:L:4>/Qj2 I6)Ts]GHhl9asdMz<{ >N>$6F:WƪnRSq/}ȅp|>_+KPңd$=-IEOEp^E=ꛍ 6.Jt]^2JĶ|=i?xP 0Cyة)ݍO٢Xzbw~ܕ p#g#?ۼQ;Fy2 3/HscHo4P,xmZ*Ȕ@~8KOcu=$[tك*揤EN d1PLύzL ԅ`D^GzP字 ,q," i$`RWB3:ASיm`R݂TED,><<+Aq 8(I5P~%hE,L Y R:dQ| X"@mE'g<4v(,$ZQG '&&"Ifk01*I.ˆQH/{HQ"j4v;=`.*7|8ϥp)T hGV >v4wE Mi1]\9T3ԥ.^vY'~G#1'H!Mf,\{"dj ECN.۷׳}$5(z!zvD܉El7>8=n ]o':A!Ճuo~2@$avUuRf? &;Q0ˑj2JE2L8G %S: 1)QW (f t*)D?>JFݢy{$mjIo q2o胸6zdrc{erΤ&T2AS"SHL"^k5:CzuKCnC#^[ӡO1x(+:Y#_GLlk$%2Ȩ>.ɗr"g@= ;OJ (ȑ$98S* |FI JuzraͤwiTfBHuHAViHJ5JRQ2--֨ 75:Q F{Ԅ)di.L.'ZX6>K V!#*i#;].e#y{z?ȿ T8i AWd(`"l%8x'GѧproOv3r/=6!JhTZ2EwNIjxXR@$%ԲTƠ$vKt2BZLi'ƃI!+- 2Lo<"B+n샷ȪLJzRv&:~|I5j]7A=+UqhkJRI{+d&9TS!I,Ш҅OџlªT&'hJ* DXt:$]bAob%B%JeeF+I6XѾvxkuMΗQ)@\;N9L%?z d5zd̓b{~CMUbl'MpXkID(G't }!ȴ4檴|嫅.7Fd/{Jz_uz+2[SmǸnEȩWt6Z堏 Ġ+V2bYR-@Q@- ZRv٨42 u_PtmЃyx;?[ƺL i;2LD $xiѦ>M!NJݦ AA͖ ϴX%JulPɻ@-vPJe mn uYF6]{?z--n׸a:7~&u9#T޶yW?rKm^{o.6oBM n t"#]߮}wE'=t?kBN_Go|\;OI@[TB˧_aU6$W@Bpis"ExEXJPD;RG2\cٙu" YQ]JBLJZ:/TȔ9f t;eZL2u~ Dz5)U,`znq@S鯖?I &^!oyS&AwfçV&|{F6( 6.7i6}E@=c_EKz>߾' |eK#FN֡ qw*K=#5&9{@d#.҆O1D=̒Ρ _ dG=Ob{vnN"MBh'zZI$T/x$V)3L#VZ)2"vLa'!6d]Fںc+%*-{ Eh$.@[")U{:'AitW'Q(}TOgfhۗ"" fTdDmTe$(- "LJn(.VڰO峓%ڠ'MA@* J}O{Q~OP+mIk) GچBAi(eE\&;vBcWp&aP 3~E\&h $hEL/!:I^%ݕ^|燆`Yo~>_Gna[\\׽ctz& F+q*~lqy(`I* ?/r//Ye3O~"ҧd|屣ŚJ5髙j ǤP &'*JEq,E#ςSyJ#OǤO=qHrRQ@?CJ?dlU"l&p< xyLQ|fq#BlOY$_ f+?8ZPCtqSeM_K;vҋOl w#Ì va~vV U "wR I~N~Gn+NsP^|SuPyAxzbnG H䀩pgLړ;\O->ޱÝUE%w$5ӄߡovY-v/QiK~6B څ?j\uE? e aHн{XAJP-y-rO;9o \p`J2x4$8pxQ6jEѝv4%/@ f!B^|8 .[=Cit6Ac7$MEY=B3g+7ֿؖsg?Ï9pߥJm *f[xSmп@70C/$w fRFAt ඖL ENq4Nj&*5iwLf|d çl=_.~ RhQ:&ـLork58^JPM`GX/fT(|EKb3/^K̃> \+m){T\?F(kgpBחf q/Œ1}eP(`<Ů\^f Ẁ( -o=[qp mbDH; !B}vQ. H9#.%}Qf#J֋9/F k'9ᕏY3ݛt-imw#9ESc&ؠ%,7u#OfOtGWb qb!:m^8rh_z513Әl 11YSypSMq'lfl_x k5boG IrLe@Fƾbg.(("r2sf9 s2\kՖi/qS3!HeԝˋRZ- ʪjtԺ=^WCƦN2k:܀E޹rLY@ XѤѠ )h-cW+ӓiDR2 V R($/ Q2T7#.)!f fa7$bET~sQccqeڴ\Q3Zوf+jM!l,4k "KI\U- KڐהTe]H[:2C(E( jYeeJ{EyZtU u yYnw0#OK\$ڰ8WWY'&rgga;eF3&'j;˫*dLiZJf/ՕWgVTH m W# HmCvIO(kHsU~RYjXCZeAН_/7PƠ([Hȧ|%iR[MQRQ@-BԗdS)AW[KZtdY]5v_')rSTLpmE-uV]oQNYcF% *sDr8+֬2NP_mORm u5@0k& 5Q덖[^5\\q 9]["j@AU2w1AD8Tԕdi\+%\_'\,< o^Ԕ)Ϡ\e|Wvؗkia+4I1(6ن$rBFQes+%DZVM35گJ\eb ijEʙYqvE]+|j MAO('^_eЭ*sYV9ML4dՅ U U )Se*g0]':QyR%,ʵK DncnnZeזSU4MQQY) ZUV gjCיfTrB|W**[MZ_kphE{fʋ,_l8Nf.3Ubfb\т٪J[SetPKmȚjh)+QUjIQNhWwQ-5DF])QAyR<&ZeaE8ꚜFsg%)Փ/7[iІ¡E,b[YPStp^=̮+!sO}CeU{Y\}I&Sr+]BRQ,*ȏ"]i(ʯQx (ֲjI y̮vB!ίrBD{jU'7*2J Ւ@NiT*,3Z^ |؀tYt|`V[3Kjԍ<1Tfn0T{@>e +BM]Uْ] fC2 s.oo(+*֛Q-i4RnyHFACRsD.jE> 2U Da4יWI%ME;cO1 VV&][F4`l E|XK W,v!IG`WIj;5̪c9Naٟe"oTbю]*/zKt35Y}Vm;35@U#iwWK󲲫 :-dͭ%M4Z%I]D ~ktʊ\; 3Z='\,&G!Oar\ $/Ss0Z&2"8P_#tו{\uL]Z\_S!2=!RXIh>hCg.Q%p4C[]ˬQUe8P[&+IjX3K4#i3}Aw~^clTy$E0HTZPYQg6+d3V9]t9F -ŐR!?+ %E޵#Wk̙֗jXj|TvG㇭ #Tu2XHB&v4t pf(ܾunTɎUbk-Z}RYIYgF$\ZpɃ5RɁ #~8Q5Ї~Ɯ/; e ^ҋ_:M1~s'UH/ EQjM(O! l2+T_"%5:b}Q dn,!V[^JTjOuhiTYi!uR*%zՁb4X'idJPe>oA%F\oZQy(R7foo6׭$- @&$h| 9PL 3yI(8e~} $\\Y7Nއh.Y9X8L*$ =~{z߫Iv*5c[Pt sҘ3DZ\SgG^vE뛙õ?&z{>N)Kףy{dZޖ- ]n6iDDL~xs_WP{w{:?~wW|߹n"pSO颴")RgH(e}an!Au_;+U{/v>ULT{[XtG7٬Kݻ_]h"PyC՟\ȞD:D ؘ}R )۫[a@Emv }3'#? KAy/J552xi`Q *.Ь`KrAݵI#XIC:bwkH=xCU(ܶt⩓_P0IvA'7u Y"bgGĕ`xPcƞqd6ܤьGSBqKWr)on#V#!Cj\1҅$/lVE,:?I99Mw`yxM1ߏձce;fA5׌Z oj i u4}h$ 헯N {Wi꽆xAwF|V/x1 j WgW\X.aU1^2cUuৣb.FV_T_W}dd .8z|,=9py<3l 情\u90aCy{LEHӜh{lnYuÍ#3Uy4DIuv4pHC鈕rwTҔTa0**/JՅUgj†T `l`2N9*ռ0Xy#!8v3z^k/˿gCx!i.xaVJ[f]_S%ΔmXg٥ K%ۛK y)~4nkTRKƗ/Hߠ$5dH$IEoI7SF=Bq$֘9b rqu s%뫔#QJ26cG&*0G%57<\Ho Bf%RXMB uY]蛶11|?WY/.!HN[3>~y7nnbCo M׋C<~l@vrǨ: ٝP"Ƀ [qE/><S7˼\vqp;#݆̭ }%8qSod`fc^lȆ E t;6!ErhʛRG7WT`5g|u,iDU1p;A Z|~0Kr7$3=.7J4Oc捊S 4vxoa<|#/DIs2RfIYO$NLS;(rO' }dD4NyW@2?$8.'] In A7)$pofEtsFSh׀0]bgQ*ϧ1=j.+@(nCdG9(N.Uv5&ЂRʳ<}xVܥ<'IY^P>.wIxfj꡺b"I }$VE f,qFc)euș3ݮ牭r& R]烉 NlrKU˳.d`&l41$ K15 )͸х.Z|ZQڣt)[vEjc"IT`*C^xp݊ %/p)|\.Cnڥ 3@玮9uIc[ߞX~^SB{׷(b?){~"l̸'3bqhZm!c/iph)Yـ̯.Ip@߬} lWV?HɲCk1}&Y#s`ɦ8M:xpl9Vz V*cQBbBUV9 4jyS^#7)zrL/7# $$ty^%.Qտ1x&߫x)Wq vܠvqU%}sHr%7ɗ7N͡޽L3>߼r傣㐜~]q2udBDEz[PE6$vV7b>9Ԝrj"\/r=i ڄ(W=%<ݙ8jhyv 3 aMd?~b߽_Qצ0@?qwm}dLP{˳ Sy//@0q{ 65p2;ҏHs#2绐4B'/`d1Øܥ\N +Ϲ PTw'cC6e~+145jҀ1l=֎mO}YA7,k?ݪq ylm$?pcYXO\y}`B9af2oTy[.>jQFIڨ5d|XƳX_91XqP/k[渽y邃WMpo\\o{"OR_~)y' y54Ŀ޵W+c1]tԼCK@CHW-ŴwV#'cpGOG\A"2mtf"_|wH`@l {I> UcZW N(7 SUfZ޿`ѷk XuqJuf*zœݣnk;%+a!Yočn/4R3t$Jޮ)yF2􊐤a}kԢܣh)w#D jQwa0=DD<'wBW.nwR#'q?V'kŵ,xfi ڱ_&==7|5jg}[o0k1*9qm4O_B~q< iDӼ]x_ϮwCEx׎{k"<A:tT)D,:lߞR=(k7Sm`O3f['hhL߼C8`" fd>w8{\gSJl^.nR0oc!3%6k},V@0 :  P՘nH rN{،1UG<IЀ7oVa3R#P-ސ7TH -" ϴיw6!ׄ$\j-DE(|t?YZ~s/]>WIX g Q(њgcmx֑#5bN\SkY7@qy؀2$ . ۀ r K6?T)&MN%F;zsJw˸UXlXe@ڣaCۑ,S (cRECRCry9p`߃zʅyυBVciM|mib~;ʆe?ҥ~&'rQ4˫H-z,'= K&SŢcʢ Qy9i#HLwV!McI#N6{~ y9$Iev\>"7ײtlqz|n 7\*SکP$Jڪܑv9we>Y;<JI8V[1 N{.pE~:+΋hZҽem^DpJH1 a7-(9b\Iތ%8~#BU>BG$JIW/Ms?F\}r8ηc^ [Lo3s1+w 䊌/fӈ2j.mSH=w"}|n|RP WFrSk]ߐ?<-Z /X̵mG L6ZKrZSJ]4>.7]$!g+ )Lze7KRx.Eiڡ̲OD+ٚBu^,Zʹ zP{a' ]oo5?ucוZ;wg(~+۷?)y ;Lʣgn։ܓ\Zɚ)QA;(a<-m%#>(4o;F>vɮ#q`i#62Q7ZD R( TUtpSN7,< L!ElѱRy-޸ Bu٢'ISpş)-1} EQ;kx{W6f8Y8Q( y=*naW_ v)7!KG'{ψM,<w)!Pzn@x\߿`oKh9qT~op(v{y8w8~j9N#$4PDBPǁ]rZމ{2m{Nn?h &iw湑a=wFcFeph),_o1Ti38^VH`- L6{lC#1M-7i9#f6wnIH:[?~>s-ڟwwGJG4\ʵh<%i.-[6<ߑ"O lA"0.hXHe m;>Mٺ-O`"" Hm*\EOe;һ{MɘE#Zȴ<ඤ=,9xQ85y9/[5;'tD@j:ch!KwnFD?cPv:Xs^(PaESq97[}I/?\;UJ܇ )!6㥰a݊)Q(h"ނ#ֻzώK\ݣ8QX!s4=\z9$JepO I~G<-V|DasSo%[jǷeOg{7**JOBh$rɹa&z2I HRYA|hJM wNKsW2޳=5` /z4ކP,ېHD'ElLHW=zd,Y1o'Sb|olȴAW$Cd0T=cN:(3k> օ4|^ >zP2sDZn˰l?D cqhzgS%)Մg ySCHUZH.4 ҳ1"^IMw (kmb/˦Ɇ ?K+ -Fbݤ>XfZy, nYW 9|j5}sbj=SvK jF6(=:]܉uYhㆬ{-׾W8Zoy.BG Rs!rxEQ0i M\w3U#UBӺ&_zy2kDTgH/] &~8os Q5H]cSG;PrJ ݆7Dil@H7߱|;޽_M=oҢBKݷ 1_}I:>dxҷ4鸸m\|o+_*uP%|w{i~bW˸kTCx^j3hd W䪋;Q:>='ZÐʷ,p߷a;:kxrj69%:[ᾜwtMiPM"! ͭ[}X> 2Y9?\0ib 8y\VQ%ܒ8ڈjbmIL/y/`gͮoiENH ?F=F5sYօB |m@z!EsFj2<=[cbx((޲Im!2 ̐q:P";]8KBdB09>l[Pk-C]b>OE?{W\7l+8a\qk_//Gիͥq8\9>D/=X2!1_@IЉ(:7~\t0˥i˼:8sBbCzU} Sb'ш0\]؏6`ldo.p\t< a=ink7 ՘~K`Q\'K=g[BE([D6n nS''lAm!df{~aHlQES脣DԝzZ8[9"lt5tف}VqǑ,l vO 7=/݇!:?Xձ(nuա[Ep(8R̬gߞy*S9VF|/d>_=pZTOIZ^ϋ"~,-Ay]l](bNl;{0`f.$Q7}\EAAk1wɄWWyKT^o-\_ ,Ee%,]A0kbRD=5tZ%D h?+!#VJ走 E^Y=0mƗ?(Ґ^Ʒ]@B]ƶ _Eku|j 6޵ُGz!D/OpyO)~߃;+5=|04 Yv Uv#qڦ:tvQsHޮ=8kU.HπQhؤb$kzW[uB?FMJ=ᚨa*Niptfۓn$wgqqxLzِeBSM]<*! }HxZ@YȉVfofnm]„S)ըi%r[8잉vbu&Ξ]:0vSam*q@tQ+ "'Z1A}&}FDgɧ&3U{)B/NVn%͝omyĬg׼A:,^[F{\pNǵ1̓&YOh^2lx*YIЗxz~Yı2UQ~ܑ]VH4繁.⥁! xYT|-mkNƼWCܜS"oEM\jb&8/z4z^?ICA1WG[Bk H2'l_!I["sE2Sִ[vy1ٵ"tܻG笁Ҭ틏>Itmn5)#r.@g2u9S{LIᡴX ɩ:(JKvoH/w}8ސ; &,A-`=5gR6 tAE9yY0rsVt r1j0-AwQ8g62=aJ(@ػ s_ )l׹\oqEPXf wpD%BGa3 gdHul7o01%*kKaPНfQssf֝:@"֨mkouymإ{t Ȫh%)=K.⥰)L \&f+#>J6**N1+JxubK 8 G9$TL+ hc _﹧i@N0WM-!ϔ>՟."K)=S/a U|8G@X1~m0a .'8S"i u]}7e}$Kr0~{2+4hg,LH~ٔ0",: 8'i.A L" P'Vwݪ*$ӝF^$y.$x8{[\,𑱆 t/UnX?wh-b,^Rsp#.㙅2fO:QGR94DuƸ*\ DIFRzOlx2'4*wq p?CaϗF.7hmΫ}omvb> _Ci!os7L"˳̡.椼̤z0.8R,sPr*_)^O/kVM59|U-g3lqUŭgrbJ}~w ;s߾}ӌu#3_?apEA2>腦o&Sll ދO^>ZD[,A {cO"_)4X/Zf}YklJo.gcLyr*d8[9^×M^>l&+#v6 |Z}9>L}vkS7[,o"|3HdQ\avܡu*qӛ~Z0Uk_Xn|NAz蔺m4[{p'=?l$6Bð<<-~yMI接6 Z&|[3l'7 =hDC tso5? L==X+SQ@ "N6c-51`rS@P-z3G;KpRN<{@(BaŴKT,_C?0X ;[RG_NE^".W R>_4WʼW_7x]/x^L<8{ &V|Sdw|G&}@|`- S_ (d#!;~}="'q:ſU[.}|rm_|;i1W0Qcٙ: wr/؅ ߫c=LkRUK:xǭ#!\Ʋ B0a;ДN2MF$S@.U]39pͿ~$o<jCkpʼJ94p][UQ,~ ʌ8hLz}{{ܥ3_w> } +Ղ4RZ{r Yap*%yCFn`џp`H8w]',Ԟ{/*Ѧ #}UuZPO/=Q*ԆlWZS滾-D2vX {u8HCXaD!j3,gʔ^WQVc_mjcach{|C6:rEY`HYt3vpЉ.,:0T<蝲5&NnpN13hX} Ze4x2G s+wLJ3rGEnuk?Rͺ}n/P]5̂8|öq-^Cugӥ; E3Ȝ<2ӗ&9޾}i{DZK~ ؀Ѷ7pmg4,| $4A$%"(<8zJYY*d^x6zfwv`t`!l 4X4W-]bd]Vk-͌2R<>YmTY-QhQmC j ϞnK o=o}@>|I9~܏o[b=鿒~:4/DANEcmaL6abE7lx UV9D[2E\羃ws (-iಟbw؋_Ezj\'rLYvwP;7au3ӚpkOi &(2Z=m+[i(0D 7zWaגpFl S#~\x?nmzG׏er_"v}^\KiS+s^6qxL]@xCEO+jqa1դ.v9{8+tS9:B )E*PALeJz7C7<#ᵰ 5j ϗ6 I7 6RG`xZCJZd3opۤ IgT};,_iw?[e7KAZ-\:.(Ic/n|"Ɂe ?d<)l9MՒ?կnUf žᇨkh'S훲>o4gQ@+@:4~n7oAijv O4-AyᜣGN%+|y+ME*PŞ͟f*C*>rE1o :1%BZ |,K1gLqWbN]S#5жbujFWnqsvQ|Դc,`,Ag34lV-j{ibCyz 걌0 ̛*dN]Tt`ex|ߛ[G ڥ`x+&RWrxM'U}ERuUlӽGkx9Cqd7{9\W{]_ixC#:z3b0sBl"|M2Ԏ,,l)N|FƆFҦ G5esPC-AYۻpoCv~ȭ%i׬73of7 ,DKTؿR?K?2k{P[1V?Pr>bZ_>{ˁqh"QY7w2;fjG$USHr5hw R|%&k ;,UֶD)[bЇ?iMUAv~ ykfeܪ@7Rzl8pӎJbלjDLy_;~# sS2y09'A< ߍeꍜ g﯃"*WWf) u&\6uCcL%#o:W䱅]r!FYBע6dB)'ٵB<_dr-Yfhc"BF -0=xN[>Օ}(_Қ9[YvXWet{bpP^5y]jCQ~݊lUeƯBյX?Sc PߪP}7}+'hS"{_b6л )ַ.k/:'BiѠ@6:rOj*ٽ ڭB(p.w GI8 :b$F"mU958HNc|pSe5K y:nɱ u*R`zly Ѯ!&r.ok)VפV˜?Vg2s* 7WUP;ͽWi,¼eZN7N uN^i:+.DFv?7Y\k8Bz#"4 1q *+7"7brն~Pz_pM!Y[$raJǎm$95\4I;3L4W5&_`fs3+=f/Og$u뾀֪fa8M>6?!]ؔJlռ:7 /^|8@=in)c>ǵ/xY2tɳcϡKOFrwNz2Ln:ZV-7_v:Əas uEbFi&7sf1 ,|vq gdW>|4 9oF}e$9 KͨWa9e@0i/׷Xͭ]; gg1K-s#YX;@_~/Nor瑥EŖ2җ \49p~wmS2(v 10!"{ޣz\`#\! 9g%ϼPDI4^!PBuJt͵Eepͳ97;Fi͗(a6QCmpag:'XiUSjTY͉1hW6J`Jx^e,lq?:r2Dvsk_5ٓ{":y!\oʆ\X ~TdS17NBgyuh`o&捔&q*y?ŋcZעQt:,uӂdrb'{)^,ԠVtg\~ Tewzf갴$e9&ژe{L/ 7E1p5 gmdv>dmw|RT# ye$ 0)H[NJѐ@X|ޢ [N]#VAN OBPk4F$Uo66#uu3hUḥ:5_>B+d}?ZO X[6zөN x>fF|>'閥K~{t! 4-]:Fό'КSX ԸYج&oɶ?;G 4YpVz` $̠뙷I9^D36Lzxsx{#WWWx+t TE\vsf+ o^d6$8O+:ա&\;d~z!v^9/#wlqc{w.qs1ʭItw^@}I)F1'yK徟?Glmo:Ya@.kh{-#0fU*p[ٸ%v07DT[Ի{.g2X^H''u~2#J6*x17sS8 ΢Gyp~z/Dteuw+z JHY>UA* CHMpws*DΙ2[S>c'pn2Sy_ 5rMaE}uu%=<(;f"g{ )z 2RQƝDq9P&! >^boܕ[{*1WƱYgT@^𕼇횉; Ǽf0V.0, [_aw  _\%kxrf(c1v\eK0sQ0|9vh閅smXX؄f*`5*r@} )% q}Ew;KPmeYURZ72U+V NIG\׷$w/W= SͩN9%-jO=Mj_@Mm? xץjwg!Zkʝ3pt9-L)dfқeofI0H3l3;}cXZCy-*k~zy}!ǵ0qyV3i^;ƽ;x!{p#-apZ?3+B?_F`|1NuNULGg2g~GFS)4g݄B\;t)S ji%YXC{Rٕ/Dt{I>QFRTV.+XcQr !VNӦہpnѝٯ`o5+NeNxibXU&D+ q:amldoζibZPvr \/<6jܝ.J4oM6=Yd=Ǖv w'xQ؝(kPҹZ#w@?ed9`#.p7щ_=̝ɥ/  >nX'jwٔf͛{CM$Ch|/D/u|T:-4jK<ҭkYa;f*3P$EcỺ_!BrPcQydNn,)V\c^aHO #wh1٫#EshۙQ i@<'d+ׂCrax̣ n59'\܇jP*ShO=Wy`&(;g4E" pTu7R2$1u`hh[O|S7{Mdf"ޟB[w[ѯɯ 8I@;Ie#9&Zd}T7GFAڣ { }#UA0j+'1nZBI ?ĩaVZw.m {#W<mqŮ20\+U8l8,NڶQSEi6ppnÝ/&xrJWF3g`GX]7$Mv< +ocYmznɅp5fH^8g7d#%˿evm*[+ '/)l8RxO[ܚ eI)ڙ)KLd_#+ާ<>mݓ#KQ t,(ϑoW߮N7g[rU?r>wtjH7G,BOtV4y̸Vxa1$Xîkz~IqK~nQj$#ˆ'\g%nٝX$O vo#:eLwYoֲi{ %@YL0WiuK˕ YUW^[#Uחa 0B~Bf}Z4Qcԁ" E%d yElzuj,6= R$WfkhSܱhϱN?FD'Olq;t" GW_DH`A88't'تi}n~ihSDhq{{1՞9w.W$X8# ef#*UjŞtrۧ4f;FVf=^8Mʳu.S)HxiL wٝZh_.3SNɪ4'I]j&fVNxhm8nIMaLBr%QC.WEO޺.(@fjĝ4ș!ojzX4)Ee ~N6kѵZ׌`p3m' a/wߑ@<&aq; ?ƹf"~:+|}A(ySTk̴p%0 `_س6+pxkoKu7vf%kui6,[n/H^֛86  G23 X[x6B|zgK;Y0B`{rS_Z6uЉB'趫ws+e"^{tx>{rUX2D=_9, }@`о@d~r xm4s X"R(0wʈ=[vPVCZkbа0{4WvjQX{kݗ4oųn|߶X0vuؗtV_0R1bo` w:@rƒWv R- $`d)g1q-"yغsZ 8P-Α5=E4˫b= J]/o=WOBMŖ|ga$ x*Ƞ +ˀvp?d Q4ޚ@Aya{/yε9W<$G=A.#.Bo\ Sr^I?goFm߇m~0il>55%KD?_G%;=KMzbUqH g/` `_3~A;S_qGs9sUC纙͍?Cv.p lD#xtu;rƍaI:$&*}eGQp"v"?7_ul8o +y&"ï ؾ5g(hrŸddݠӄVIJ8=d AEdk^ZS}塞$oii3L6}CRd{ۓ )WG|z Q&뽫9A66 $TV|2ͨJǿ'r3p[ //[~\eǾb)oNi^Qt}`TBMtkr>ֻ:!H"qW ˿MM#GhŸW!2N!`q3M󿂚1SkKČ1'/ffĊd _e$!NExܒ(_s.¿X) + "V&,.ĊH:- )ЬvRSJO-ͫ *W_Y,5X}خ(r0'"ˏzI4kobhʵH'VqQCYBlEӕI&}s2||&iϪ@ *sM@v$;y%BjB׶Ҝor8mFzﭦ7 O~o'y>!\s7d..ŏ] N(ZDH,lǾSm-:>:(N5ӦƸFƟRKd<^f8Ww kndrb%/DLq/2=JMݳj]rW7ᓴZcy =#C& qsܩw@9ύ$F LYrruclx>zXa^w⼌$g^Syg_eS5q-!a!tym1wB֐0lۅFe&72dPoA 歽fQz )2jV^-_ƎΥB^lkz"r{G2Ay?[Ҁ9be.d:@lA6s:a/*/T4diaFWNÒj=@?`FP۲:y1,`=oJƣh5 ښ FX)&A;RkB@)ÚaK*tX̦yj<c szÅwc::0zټ"25rcXݵ_qy8=jSɮ,VIGԔdZ%NV'an&S!x>r*uOBϚ;<7fuGh3uC(Njف3]^rNFo!Cu3f*\y%RAmc8Wp(}ȡhXe/#N誀] LjmENmU  8ў\n/of AɨQҷMIilMp*<H9V]yb\ 9uy}nפbfqjN9cGNp-eu,ra- HY+֧w ۩f< 5c2:e./׺d\5VIdWI!J>p *![||˴4C ]^6ަj# ϟ0EazG*B\r{;>!HjmajhMF_J7<`f-*D4M 4ÞHXeqߟx~GEUVsĺ<—U=RD#ߏw{tOO(#8G-yZehY[!fX~5c0G滅@QAxk&+ 1u&]QqH:7Fd lXB[)7(_L4@v"+}bpKe/y1Cm|S-r*sR G|I@[޴ A{=l^YjdT=Δ܆ YÖc*70ԣvJ4=3>4uϙ|shͦXAH&v_) lm֫;nY[%+iD~)`+oU*Q'/_>'Z=UEoƃFqG&]ɜؠ8>y Պj!zD[3nI>3 y!|vkV{vw6(PVnoԷN3ύO{xvsHa?g&OM$ha&6->3|{Gpr&[8tR{-%_Qxq5҅O6/\Sq6<6hۑkq]j})zƠQVAۜ#G54l5\Fe($JIX^X=];lNO1Ld 7ZPzkܼ&=؄x=IF 7j)HʓG&&Wtn@gȸOjrd+_sBZ$ۏSOA.܋MJH "7ȣr͞/DG.+IX#'F^1 qHkLe1śɯ #.GjPhqF<Gb;6!CE,2`TˆYXKg]},v,g䚑IA@ҴJI}}_&Tb>6΅UA>y.6̈*#' AARJ'>ֱh?*W#o> o. |PV,(.6BɩQ6~tfh&E&L ]_ӆC8$e*8<vuTo>p~W\zKSqughڤtE4R Scw~N:%vc8w(UyFFww#Bƻ+ˋ8m2BɮÕH DB&h;e8;Ɏ&'v0&dXqn )&?)L?Ay)s2(>uRE`t$}[SuROQǵ?>O}m 8ޘg֙V3q ԛ/:7_;5P@P2TNp }6{G y;_ >3{knZڔ5`6la\8Jo9w Rp@1\j|klxpbR!ڶU@YW@& Z%Pn R[L=u4nlyڷ$CH;GT>#vЛ RM5 D;TO`5qen iΑ/ggr쌫 ܪ_io\Ψ.Sa/]W?3x~OG2bP=%bp>AdF~h o(0tb~mpGppxqmZ2 ɿ$ iͿ4\揬/ YOՁrgH8 (^A*_XKcB%I]5cu%(}4[kb 7:4JclޛB&yGSIݭ\wy{'pyF/mT'L'W:—z[ec&DqM-ڳkׂ|!5YQ7 aT˪_\{qq ~kCIWQ,VQ^c?I Llv﬇< -Pŋ~0a>`K(L|GZt_"`s_ɕ=Z @-Q~O|frh:RW~1=ܫ\ԮG(K [=*f~>IV?/'{n0~ϼds>=VANϬܰI71y4^*6Qȼ @-r*&}jtWh&{;}nZB9pC"lQȗ}+2eom]8S>$CHъYpG,{]Dc@XZ ,Mg}6o`ÔL ֓p#ٙ^Q3}C0BlÕ\ +Um!w#WNpb]p摃+'WѨE_(0orʲu!o[;M(RgxTq`OGŘ/b<ʾ5d'ngin=݋S_R.nעq$j:]p.@ `3V9Jj>i&A^.v|I(1X6Qq汿_0n"B܎4Gz 9! м[ O>M2|kw쉶Pk|[^,+"')P4w'-e*ZgW|mVi]U eK7Lqs#wdpTYhg^ypG=8J `fMyX<5kfHₔILр<δ{֓85sbtO* F-&ffeeXBKU7%gHfi+Qk(mLyRӬE۸=Yޙp3bCHw5  AkKaƢ᪫5@%٦׎0qol?PěyQTBAxxB4悽{k|Y`G ƮZ:ZSfŷ,j>ydBJsr B]Vs&VG"QGqsVO`cN9lH,#Cy:{C_G<讨b,k},$&+mfBi6{( SV^cى=i:b;9: š.N#ȇ #MնtIܪɪʴze7Jzf4 M{x1v{}kouE =cZu [js Xa2$&g~JL2ptrr;|"ȼf`Cnϲgjf^d>f>مySFn |6Ab +RZQ&@)Dpmg|בVCNPN./7OG{.v*ٲ({/uwaَ̩&TJa;֒&ݖcxr@5x̔0Q 9G}){lgSb^)0 $ùt8.JKZm_d:!Ck[]-s=m.}颉TʑIV{(k-8#0>t-%ܣI5Т[oǐ, D^0 "7ep2H8t~f +Xވ1FjeRϦx*pH7#-I?ȸqٶѣNtm۪B)6͛;z%tvcP%:K/b!5K o^+I-C|lGsg^4:sWqd!Mڀ9+0Сr"U6ݱ3.:57Gj |5|GNjSr8EfoHwK3 vCǜ\.(UT_vm^ $ ·[c*.S綯+I_Tw}y*ǵE(Zyf{<JrQ-bqlԆ5ي>d+N-% -I.S^5_j܋)3\5h&D+&m*g zd,&:*D.ߪl.>85=z.vR<ܝ6z;ja[E.=0`."PW O,] ܅wfUg[ qq>_f=+cS:Axvsyj/%@eTbGA_q~o 0o>b9CW]#wO0_8 [bH!\L2mS>^K?>/>G>ˏ|qګ N_&K-ms2%Rfj[;/Y)ͻ"P [2n~s{Ǣm?m.lT1<1uKF"lg2a}[V-PAP4@z4LW=Dkn񍫵-2 y4`!CLjy \.ռIKzQ@עaCj9#p2e){ @}1h\,W6[-2 "O$R煼]8J,Qk' SNϞ;Vv݆9cEDT(1Q&j8fl٥ ,o܎hm MF%NWVpp4+<^F tնXs!7 ];Fu ?aG%hY!cGxX {ZJC΂gAvRnQGwSy|Nqv9Cl3*%4irm#0T5"^*~U|J6޻aOfM8/N8jlt7$0bǍF0SN .g3G zrTo~ 7=uiw񒁷Aee FV7D+z!f 笄=rR`~֝PM"Bxe!Y ~PX+`JRG;^j,3<J HM$* , lFdKr\M.Wwo9jDAT7‹}"h=Dr2~=c @K̄F|%]ӿ#eS:{]_/_>gǘ^R*?ς?gOIX=GjXh.O\4mx=:, @Isv\؂4-`ςD\TTrk{ȞsJDsFqIn'pʜ1mIud>XQRnBȴ .6 Sa"t*?`Tփ6h6n%j0L l]ͦ z8jq tFn3C=~'m9p`ǵ.'BTܻ%{_lY(jj5݋l-MyU6BGoB. n{b>й' tM`䝍=DL<׈:;JD7J(јMv2P]D z 7!Z%VV-Jrt|o:(3[7uN6[)*վF׬Yq&Z੮ͫ$=W?a{kQ2J*˄>R =n*8F&7q06 ^K%L}x"N1oj ƽ1i{I;{I/61QgBt~n74@-qCo5麙Zl.Ox]k 4ƺDun@,3aSQ7A pZ$6+$t#{HfQӎ#gLciDP\p7j2tZތW1nX7m)bc6iyz:n*qZ?9T.%VՌ{~,v`A2@jH.DOT^KvDs37j,H bP]PTuN+[7Xj行fgj55ƚf4K,̤4~.+\< .펐Unž D x"!Îӵ(Յ rhH<]dfJ05^iòT>dy68Z5|-H6vm.9 +p(-ꩺ侧86D<)#Ô*n!)f`y8nD0J ɽm隘y;lX8&x,6ͬXy*QZ8#fO{$`k&9=7P<1޴\ڡg#*hV }v)ruw_e@A_hke)?gZ}Ļ1OWkXp'YBL}[(&,(Y[[FHAoO-AQ3 /ݕoQ+MP9 i:8; #pQ =o)Z [c<1ej~;Xe=?92H.k{Q|[Uo]<12AP.:L$Z] Lle#}=:~e;Q|뵌Cy-Z7p EELT󅾧VrO)tvt;L"ymjAm+>+7s(X\yǢTJc*PlI-&q\8TtTdYĮ1ohlwb[#lZsw`L֧=Fǐ3Y_ y5y ?Mمk1˭_փ7=Kc¬?ӡx{^+8k>]'..!,e>[Ť쓈vȤ*`!W-eA?om2xWY榰.Gq+|5^~Ea2<?8.TPZYśuz3N'|=ca2 n'櫭k?ADSʰ,I&Ii^lݣr`M2Aj">\m];]ӄ>Sqw{k*]{DLŏ&71+vi!f'b%B> QD9 aoC5g"ۭ Q@2 f^U=L¹*TF]6F*cRגw#DNEWkyYbTIc*j&볂p j7n ?ƾ*x8 qsW<)<ӑGM*M;1OA;v8!Vӛ^{5>gP^'O*3G+HVG|/+ܶ[|! 4)O4ǣt2p]c;@h €ە>FLTl; |GbWp=$Oغ|dLF8&9ev ("IC.D6_O,˥rgC/e&cM9'BU`%aqZ<96Zȴ`~ lRI:鐾~xe3j97ݸA{8ZDoLsh,/31$*G]wW43ܮhu&(gR s${0oݞw|}nbT1b0w=W'!˜szGyn^D`T*OōۧeE2sbj`jϋAI1y[9V`@4A~lq?"OF.XM}"vByYb^Jz[J[$;C0ǷWwb!2;!h S_[ܶ˾rAWvm6JpTwnkCRI$%bќ/R9+IzI(eFP f s$'R&V4H3N7d7HdCN^vT!wR ʎ%ߔTEvO~Ւ± h-czAe-]Ѽ>Jѝĉ4ҶCJ+M WDu=Nʥ%FG,;{n:T=q|p]4r|S짍{;nwmtUݶ-u{$ MqT0W0H5BEЭ3ʫxx,D* NwމNz`9rHffJX`? vz̞Ew"#Pg?vb<ޭg{퓥cɵ y(7-1_kE$D8Z+#U+7xrLds+o 5u CȍmK% (/XYnhƷsjG:U+(fSBNهY/nV긦jp<\6*]eH[|*ʖ씼Ͽƿ in7z6)[ pA->UbWQ5$h[A@-4ph<!' ]s^VF׏4ubCdn pj-yzT 0wCTLjn1XA_D0OGa;2ԥ-k7 os2p!@XvGWRl8J\_?cʚ44Yc$V#\߷ehC -ef9@ߊ뵔Jg͖Ӭ 4-_fߐʕn8_3DK^c@IUTh ʉ髶"BU3Sv?3uj@Po%s)]/UTuT$ AYv p91n_][W- wpfK> p:ͨìaȼTb#ۙF 3 }f;=^bwu_smG8aB9s4of77wk |Z?H.F8Yp(-A)%-q7Iӿ#86d {#$ cA(V͠.r.»MoT5V'kp5rğ8un F߮ƃҋq-F~zӫ.zM_:=<ŋU9a(9LxLWjdͼd* [;3g"^җA:3QؼU!RĦ=Gtv-"w  %p{wp[kKqAbs&9Cgqh7Y!ݞF:ǂ~ AX$ l/F|>=2gOp>e$W |Nxeg;/V1綐s\Ya{nn5h[u=}~n+*Qzt ݍ"6X\ r!#ɧ cܛ>^?QkD ~G;hw+mOUonK-lcG '9=qy=fmWf#H8~e)Dp܆>iYB%8<[Ag i XXFQK~Lqt;+PK1r<1 6D'"&Ʊ>b.w?6:a uT56åR4O5hgxE%&n`>CC]MC=0I T'm X6-GW-i 6䁃)H^<;WˊVEBx> s9^*Ra̓ *$ڞ[{u}E}VjQ;l.ݲ;b..F2n7}'W=ۍrti٩vg!M@: dZclzW5T{ b[N7V!;Çp“X\/X\ b(wf6rh/Bo|~! Ĕϗ緔PiۣkqH8fRHsO ]&4$w"3d݂ kF?d2n:䟾u_OJe*)>(x}6L x ɴϠ$!%}ȬQ3`y)!*t4EWJ'iwq,IaݏuB)򉸨!]cssʇsnb{8J)Y6إne "(S <~"O{mr^Q)*6(r3wh~Vϲ p9퀺wh@BAc7yQ|纵VTgQ)aM!Īvk\02n ,+>U4[m[8,{wx ,@uÕ"x+{ ɆL)+ꞈN@M`v 5KVEƯaT8_Oiߡ eÝ'BC s dӯ"A r[)Ud3 w8ݏ{\OIScGݍX*lETȀ2ӛDg. ͑bQq5.Sim0fRltbE&ri7BgYum[)L6z牱v4_Yq-O58F1qMgQKpqD]=If1{/TYaKr8?te!n 4yW} **zF~NZ%@&!%=6r)xdqbg owm94/ /M9Q Ex9&d IUOHe󸇌޳pM [C&y܋oC,Prأ]Kĝ!Y5އ򞈇1M[ P?ȠUV-;HfE)YGǂ8_ej W,O ^!Hyܚ|NxT%nWC/cB4xpo8rs۵)u؉fJRN't !q2z{:'QvMDh[تzy"#K҃t*$SW>LM$C}෎KYE)6&)rED޹yܓuwnfہ9UJXu-7 OX\p>ڥ¦tؑmn@:vŐfd;մXhމJ .R(v X]i@OmK {Gr)$D$# ƴkXGC%i_x8 cl$ AJ%ELo=`e)S&1l}[MvMoKZdc۪;п4^ qmZ:3tG>FS7٦ucv"7Why#&b# TBں/ˆt{}N17;{)43Tϊ4?z cG&4y yCQՍ$NS|O\/&H6ںȀg eKk.fL9CC{ c5MC t4C$k-gbP,r\M[n!R*юr8C>AZuo 5mZ&VÌmϕRyΪ KKϠg6+(/rX@wo 6_/J&S+/gR -O5g @ˁ&R\8 xY3rQFE{^(M1íL6! vcZ_Uy eޛ:I?O4|+Ԇlad*-;@A,VVEiĕ\-osM4f&Hanj ~WZ~V&]m| w daS75[NυM%xATt\'-ܩrwq-6((`52|Īvoh*#h OzBZEɓ.=%9[r)r|B\`T%1Y"&ne]n?US{'y̓|4wĶ/bJ[EWWϹt);^Z`#a3{ؿw=M= 7krQQټ@hY{4嶩{t\UzvR.յ nZqO+!IXGIZj N[r姙ZA.s\vq0ӝiu_]?Gb *D|/ȳ߲M!v"65+  tG dpx"^kUںj]'Q[=5"|j^bC l,ui{2( g8V]$_CXZ+X&?Ikk泝=nyʤGۓ'/噟Bew&gbT2d">4Y)|#²z} }W^BWw;xYURuzF#JJl >ea].+~q]~Vڷ?kផ"zFFh9.)P5Ƣ-.!dA3޵Nw*:fNሏ; RNڧ=N[˪}GI4QYD}L=]VBLυ:<<è_pHLrVgEw1_ 7-SWү>jO!c-oR^$LL Kc)ܣcmeU/h2#N~޿h{vו<;&`_FM}:/qLq̃2|'[? ai},6 qDWRS$?uGx"t#؁z}{R{9pw;iYL^2 %8Rw|tU־. }h9xQ6! N&DHa#Oh Goh EpzwkrөlX)~s!O)}Gi^iՃր#)9J<ӿb?v] =tAk 1,=eRXSD|>pk AQ6bd9הX-ՕÜ<_0?b0ӿa~`V^|n0q_}bFs_(6ʊߵ!5ɨ 2j;m5вn㵍pO鉘yKE>&h#@I m%]TNI.Aw]$:;s_;2px))ݘKdjs<=" p{^N2 ?Uf̷8]]LIz4^*dm4E|-!:{";bOw'^CnO󂧿hdW?[?M7|U'GLWy#%Uꯙ8';!H%XYX+KO.'[W{Vr~rRw1G-o^mˑ:qAW`&6%5&ˊz?ćɓfKn?ֲZC&L>t~aЃ$B^9HmKX#yGq)F]uwhyZ*S\з]'/fv #~Ow Spr?X ֹtr(w8GL{&L2DF-+SAY.e6p08ѯg˦NRq8Z I]O)nI,6Of_fdCG2S_oM>NDƏA#}|4"|\ Q7/SF놾ӜGƊp6ecRC.N#+s9p-P¤ˆ~7 ӪHOZ'ݠ!m,~²'K?}]KtcdƱ/q0p\:7yrkә#ĭI6ψ IkvQ<&z|x 2e~h@ͱ ^ jCO5:|aa+(\kyKwP.n2c]tQ$ iϪj4$ᙋMŲs]6*w')YDUzAV; k=:$uLFQS:0 A=p[ C7=A]w36;2{-O0Ļ3 O; "|Mc3@i/~3Ga_v_$DXǜ}P0/Nh_!8d@ddTD~QraF9]{T1bӳ^C@|5z~eKagQ5qm]ٻzZp3@YT[.:| oy~V"B)5$J"F P{X"OLjՋ9bZ:Tc 6-<Ѝax*$pp£<Ås]MVor 7ɳ;%]) 1+˽6U]ZʣC|oߡ9PP-wr6?Hd/qDg G$vXD$vD,"e( ?|7% \CI_]bH=I4Uj{˿WG XV}TX^ ^UYq}aނ14ȖL%>9j@֤='$À5 HOk{h0}rG^]X^':2k(_iX'5@k-Wb~>HgBe?%V!3&)l%"zw4IF.UPg*I{S=dLIZ>7k"(i~NԟJMze@ 6x]3o׾x< [<}}.u;CR~,'q^pO /N|Jl(%kSo/YPvظ,CpP4F3$utEil3 ?Tw}8z3fS.Qu詁&bH@OLv1?Fq1j#˿efUɇriu6J{hZٲJρg:{|F}?}?/x"I8?& {}w xg-e5^eGtN/m\L?e=]Z8jF5\XWjoކ ;šK<~';ϯwlj忟G/ !s$d ٗ^X@m0LDtah͖Ij)vQ]=VYɰnjyz#0o!e?|L9z`76WD9r/%[0FT2]CJz\kMUϙzca+Z="*n,Aa~?=9\xV}=z 5Cs\u.J_^{ qg_o(g]IsC|+{[S S?g-L|&p?>>aoM[?g{?c8|>X%x}#s>ɛD==qyO'Jl%.lK"~ٓǝz$J)F"?ċuoԋ_%^GI^aG,WJ;0,ͅf>DСE{@n{%4':Y]Z42{v7ԔLRrvɮ[G>67?v-8n-bH2`m)ƍy+QntNx#8/=;waߟOYҋyo+3z`- bDzĿ#?n/Z)O˵bpW3>.8Gg^_/Y}gi5ע54yu}yO5/'ٚaVv9HTJ{DQ>Hazl_4^(jң *?:WtL:)aV˶B$ > E.fClz=Y/$뭿^m {3 YcVSa^ |"zKdu4ȏXŎh9ڟ9r6t +r5oڸMԸ)ͽ<3̡+6K`X.<cE#E 7 Xد1`=O`Y3k8{hYg<tk]_9_g~˯vZgւ\+< `IxNt#&FXLO(No|`}<+<<}2U_W^'6b /׆ݝ}7? mϮ~*yV[ 0=S>w;6GuٟK ꂒwx~/!NPİ>Qآ/YLht\I~bxٙaUz,ml>ޘ ?x,,wj=h,Vk_Ū$}I>@Z=<5=llX\9}+ؾF%v#,~T6P5q{" f\O/[-‡ԏG)ލ9i:T @z`z`}R3V;5db񡴜{0*dJ`wUVƍ8ҏE7bwpvI4'Kw/ك^=vPZGu_n-e{9;drrUsVgU2` A^խHmYGкL9F!x>1!"0~WzIx~?[s ֧Њ2sG hz8aCp*t3C 0_P^f{S[ ^=͇#Dfip$p|m8ǯ !0R+8:{H9+F?!>[wqRznp2<&8 ڟ`wo/8v}ڝ$6lQtvWC}xz{+3wG,Nx)v#udD.zEd0PĭvHE58& Ί᚟|8.vZ17I Q, ;kՅCEӎr57|\~ލcw,DJSZB6Ȧ\ܹ?;>au=V6{YYkx49`s#D)V0Q֓rvOXMlst,S=jh0F՞mg5BJt$?dgMN>\F t&KX [>PL.BL)L[ƙ>(*u:ֆ-"rQݿ ss1hiO" "@( vӇFB{Fq {fjZDwd (ҫᮺ$TH2̔:9x -wcH-/D|N0 Map;7(t>5D I{5tS{[x׳P&%3 %yoawє[G50e, $avI;.!UW{N/'HK?^MϿMU=, 00IIz22[(`LB| ~O‚-\ں?p!skϪ9T E{kJٔ AoWZM)(N[rM+k2[]4*-}kE~tzgc)[nsܷB8'`IE^:6x ]ja9]_cij[kw]؜k#?C!g8=$JmyN[TՒي0Rz.戏Lټo ;ؓaIJc:2V6:m Hwn5 CN0xM]cJ:.L@`ֽo$b~>)'܏y+ ᭭˃ 8Lp ;DP8Ws2#gP?iz.Q4ġ0a r@|M i{:;=R1ͅ=\ I_mؾ>cr7yhY8#gs@V}i6˥ IM5âd]T9:㖔(FoKЩ57q!$j X/SfڅPkK4-Ʒ9k5֦nݸ\N}cγMZ t ]11ް#E|FM}邷'I{o>+CmbϞwyx`ȯɢɷlB qܰ)\! pXk=ZrC,';9?2d m7Gym(lO}JX+z.rc;~Ѐ?λZ=T䊎ͽ-=Y w:cft'ʂw#*e Cݵ+wu/,7a`/xcJb6;7Ǘ:0^euldo$GM#84r{] }DrKI7 L:a^Y*]}+yRq}sG$b"F^jsأMMCBJ՝V0 5 i`l_BKsmQ { 1>Bk7%bdof S\uM~N\a=lU7N1h 9b?ܿ Knt}f%8nS{m,_yܹg1:[=BG iL~o`cs :ރ) 5 ";Tn>ܰJ.e[s-7M&]l}_'whklw䍿LvYkQb' k5^ M Ob[[.O^̚VV=xzmD1xݳ5b>`;'BOD-OF+wk[ӵH2j kN<\czz-aE:M$i1ΚIH4L 7ZNwMۇܶf 94cd6fnou>h^[E@XLjzl(01y6}άlY3I9T^^`aFmn!Ӹt^РtZ;Pٶ$uR{!}hִ&stS7ȩ>^SavZ]vDz-#X< W_M#5>nR$ 2nMǿZD4Wyۆ1uyQPn ؒݝGp+v2myj.>a&a&]<5LEQHyAr&>؁8_q}ɗr`܇8sW3}K[G@ F(TEЦЙѝAFB 7?^#t>+x?]4pXGȍrHkqQ}= /Y^* 1ַl~SC3p2g犘"Wɘ}ŗ7-:|tR?[ym,$Q<V6-=-|g? 9^5+x[[dJJ/{rQ}sTi\Ȝ,Y?! kNO=WKg8\D!!('M}%*_ѷB=E0gɌִE`ZЀUL֧aiI3O/G U_%UtUqL?@zaDw8ONp{)7u E/mmDkP]]r0pu{ Dы S_] ML27|EW,i>Ƀk\l"l%6o8ȘF sMv /62_ "NrLsYצghm-2nDz;rѝA(mhܸY 7jgv/#aCA{,}>#F@pNyFg6vBF8XQƷ&1."['Q Ht3pkP%1L>b6`-ea񽜽}mV=W/^^|cGzy; B@I"vPv lWC=B2Abm7(Xyܰ 2b={4Tg>Rbu٤tRh]tkS*&`B1EiZf^M\_OLmжDH,ٴN}Tdthd꟟A,mH,`Č߳_-QyOn$k޴u)bV ᝘vdurFei60(]^V&`jDlWP6MB:q KU·*cjqa Ux4/bQE<9@dR^pvd_"x=&\Bc/b) 'S9B&! ڱpK\p۬]F\cLh3V^UJ[gkxnH-Wp˱ 0=^:"ɂǡj?I Oׁ뛈W9QH8:7k)Ⳡ0̐Sڥe =`_h U۽ޭ/ ǷbmBV#-„#-ܘTSbCɾWVx_.|HwέD5411kEƎ?+ͬ\yX>d&Fgw:BP6+3M nx4Ym^0Fۉw:#Xw'2#'i#nQa!GgD2YZ. 8W$cg0V' =7YTc:K@Q!Y fn]ƿe1X/&g`.XO+i($mIDӾD2 7-P~A &iȰ%/H޼-j[?w ̳$#P*-Ue K$#;VD<Ŧ5\Bxfbe)jzZUݙڛ8J_SudRpAˋ<`ppZvw`\y[,Ń\tS-L.Tf )p\T9[eQ z6*=x7toތGRqILvf>kFtf txxSoQxD|0GBD'O9 */pѤC_7`Ve*MͿg̱Z$C߂_6xN.{ R`$[Mxm=&~eZ7>Uz*{¡E0ۈFJ( (0U3乹oDb}Ke^+xWU +뙞;9Z2B%SC3p?Q1_4H~&ʕ/ZB[ Q`.~q_A' qh Bg~f_"[O#q!oə _Dg5OqW2zS:" opYO $NUCbYC5\@"l1:1%29>7X#9P:e+gC*&d<A&*qȮVЌpB$X`K \ tIx4Gzc,r*dϏWܭ$m"Փ92~&"_22Y71Cw8#K؈Vux? f)8po=3)~s#{W rS56`ޫ)UIXC>zDe `B42㖂UYޙЀqw%W2L%$d,}&Ѝ)=f+ίAmC&/Y9"Dˬ-?-SMl7%fqwb&Ѵ6epaCf9.w'E}alR3 <0>sF5#=fݡܖQzi^Ƚ OCTUi{Zl? ?o5Z2hɺ{]Y_Q"~&DwhꟋʶMb-˰*wG~OA\0 OOY' 5+ o? ZRpBKlZ7; 7$۫l^s2Րzӌ\HyQ߈j M"xߨfj?/joyŴ ޮz88.j-tdHӭmY+r),NO)Vz^.jcPLM×@W8rܭ(n{jԪhs%s!ꌋ~٩6cHyܛPݺ/xЗ3ކdP=PpM,ÅXS{~s񽔃]U0^I'Ğ!ǺD\M{3$TF@i?'_ٗ+M;r-xS߫>|[go֯YU=j(\S޳ IpwNJQ,K}^(B[Tkϯέ9g"O;u ˲aKxvÉGtpQqFz1ې"my>-mN{^֠nkpR| X(&cٲG'hxmql=fS|w 尫75 d`MAUmTV QdJ tp>T?Q#Sj˰=C[;acXL6̰FYǕnm=FE1ȹ0CUT ĶI݋2{Iĺ7^"%6jY}߷CR-Ŕ<90tbLãVLyNlUA] ,=s8{4ͿLR.ãLECrm9}ssSFQL<5 n}!( [`ᑝ0m#\W]C̾a`盔QFj=OV|snKn 6vtkܸN\c}M?R旨HD?Q,BDzU[z;필W$E.$!?Pf"!n#CoDt `L&S"͘?I0:6ڜMWii*7՘,{>̀5lٍM aFh=z9[w?dPXTvJ?8nt%&ofi-"媴2(ܨdMʏ)#[lB&OW 7h5|t,'c[mihP,x\rrFWty!M moۤG~D]cʙ$b%[Qݖw6/x{hbJ )Pل4'vK,Atnbʑ% _ȏ:p+KLnDZ}R+>A.f\ fūN5-$}_h}k򀋘0"_}oL~dU]p%V1HDBD]2DŽ%|4"ɣgy;mt@qsk< ;ˌW* m{xMHwAB7=2 )tC:3 f2Բ BZz#eg6BV^=~0bjbxƘ OhAdo6'|C1 z֎/dʹCLk:U:5ֆmwCLQVjI#O d'XC|cǺ8 EdSk_㋈o|īxsԟҍ.VtR1b&V4¹SNեf]FA^oYB|W' XKoLP*[gЂĪQ&SҬP$ߢ?s4 ?r#0k -]xĕ=g xM{԰a?; \iZ1WT{ ̵ayf&h';ɐFyMckQnd.KfS}{U` {^%$\ԷLrN/2%g|s w}5qܧyzEWzѹ~m4c),EGfE1l,/pKkbC!ONI(p C83Ao5MAkoتN ^n?B>$~igrlg ~u>ֲU| ,"S~u9Ǭ~ ߌ#۔j((raKfI6ʩvmj^c,_9zDM2v5g0 5TTR V1f0Gqa8싳Z_ ]O%[] 0[umN ID{k7fYռRaQ V={TGMn9Э_Dj~VNvh?{կQASv:|MF;@p]Lu7-͞Gs!ǩWĤY75~pxۭJ߿m;6rB32fx;]PҔ8(oZփP"Dgb]"C Ln@c c(4H촍w={5CN YmLP[pIǹ*wsz9\O&Y([pL{0ʡ8ŽWwK.إ8@xd1s ^O,HPun^g֓@epF~Wl FYR+?]{xbM (Y?|~@dW]$7FWG;xf@7=Lo)z '.яrbբ ; +o;՛ݚ|(|y jP!*̢LWv*ë'T Yv.>XR̀IHؑ!1 L{ZqP`)։b ؀zskTF.gos>wQ6{hfT b4d.ۆbd*Ⴗ[XhNGǗC*y-Q&k_ƏTM׻v1PqvXT{ u&ꯪVG̳_KpȏkXޅ H'<#O^د/q>+!%7Oы$xJGSf)+ >1ΟPn5~1M~:>Ukmn={WjJ~5“hIzv|3DC&%ʙpl3*2[ D8B,x`=t O޻FeͪH9dǃ1sAH Wap_!zvcY0w=]RyO)!B{3%uW_ 覼z<>&)P4 M֧:,>{O熻3:}H}7kU6Mm7J3\?}<@=&_z|5ĿWa hOBkj\vu+2"`4fRr_#Eb >$t {MYfǷ1a]v!rto)S'"PL|'F/d0?so㸲 tolI =@sIq7ΤQDz̬̬w{%K"k}kh仇?ztZhCxjow)TAŻ.~{x^qXmIƥOM Yb,N4}9yv -v*:;VXTWr/@(S33g8BQo.} ;*{9Mٝ[\R_ވT/'-ż^د9_d^YBgv<\c.]=(+Qӛ5(0(OkED ,5b !D|o^L+`P7 Ng[8FI7 ~&R"9ēJ0ePb6;:tmXڥY6V!-9u^]˞2N.S3"4p)TѦA=&|#7N2o4,rH;ܕZPWooT:6Ak%*hٻ ^*X+P>W_?l|x|L?F!R` *>"!Zh d*O:g?%B HK Cp^u E clO%%#HcofGVW;:xOzV"$.xt7oϦv, ߯_]]|Bu}߯/:2@;HQXVb 7{HO)ˀ10~GF@n Q7WsΫF^8Ɓ[j2 jGsamgܔsBqRpW#|LblY n a˓))-~*N&fU/̀"vk'dTN=zAS+1t̪",_ȹmov)OHZ0 }a8 JJ!-V j6 %\ZO,bql#-(Z]ĝ@,y)'MR]?ƚ[YK6|Q:<)$oqY(_R{V|bH@G_cr+j|| x%xş+03&OYВ ?-&?0{Yu\\;:z;o @w~xh8^;B.j~̋ {@+7ӳ=?<m` yL3RVmRM/@x{3sCň+ v/=KK\NNBf GvlSh_VDR{2$`Х>-;}ypx],]8šQoI{`sF81r^ĽsYl"^oiV[Zo|Q9ȷ햩x|9\2˸!׸YJnXh;wNT\1mJZ}iMW[ 8{:pc=$.D`+ ];_ @}7~l<\? ,nXZ?ÍOpw}7~l L 7~l| 7~4aFN+yf94 Z+'RLK$8uގ)ơuyr9~}ӊzFI<sqn0P/hs/W+^u,ľoʛ׷atHbӛWtN%BU½{v-wGB~[>L$``  H{w$]0(@_Ɖ'?r,rI_%`\웳Yu|f8f!r ~YkΕH ۭѧi!]vK/m;HL]3Msaׂ+r0YgҰ fZ.H;p}/d J(#Ѽ&!-Xs>h_wpO}[سBn^0Y.pȽxKysH|/>hS` IrkX {O]<3)#<5)@c=4?Gt ܶN}tS S|Co}C?Zhprw[_eoy"IccQ!?OQ 8z&P,HinD"6kWa-[=rz#t9qgĘ ~"wrK5&_#ܩK(dFl:mGom8N9I5;\/zN^k)3Ki2gn S#MH7=~`գXE>&bWE36C[1{&\ow8ٮʣ;eѧj)($.u^!.(pojOޞ;1!+{/؟Va)7HQ3!QHChc/q&dr.T`=Z>G33r>7j4]Wo2y0|J89C/ -ƺ2 @eԣ5zkL6aӗ'!_+̙$}!_>=Ff$eN^y M@@Wʛ[|Z4~UG?gY>06ڦ"VmCJ z60e߻yg Mqt5+N[1G ^3AXE\a{ccSvՈ N%mF0[`{yk9׮m|KB5@& -T|IA=ŕlksf.aPwơANk)70,e@ pG].I[s'*4I:F~xqwI\*QYW16$ld7+BbyJ$և>Kl{9uCj4o4 ⳘAÖ;~{z,!??=tvǟN]w-@Kg9fT82IT 8Ǭ]9FYh[Zk$i/0@2R$Zج\0jgzC3oIu,?[]fvׄtO5A" pUhNr:)yE4|օ8eF\Glԛ+v &IV[rvÞ盞yh ̽#.Qڼ7 ,UO,=9 *n8N֍e:u%HFuV@>~9b9eA_+:q\ lnqg5|&}W״d7yht`,sqؑZS7 ,!bTw< FjKѣkݯI͝mns`J ߥkɛQ񮿮ތE¢>kG6=܏:/}? B{YlY#>4+o˜`AJY_Z;pWjϐ8g 7m# e[0W.['8tn*t=|܍jT>&јꨣg k;[Wɰ L0YDu$L h<\6p?x1CN%v z9Y}|'l<3U ͍&F0rO.}AR7\ 0dN? _X;(N79Il  4T ~ݩjȟ!Y.q8.jVl]7PűҬh|_̸4q]%+_0]bV(#YW֋ sODue_\7,D{60Lw hLAKy5Ͱv*CnJ^;e!Dw`ѭk:!{2HhMz{1MtS9[X>.l@Vvȉ?uY5q;،Ӑ$UY߇nc@$dX|a,Nk?O~'rݥϋ^8%ϠB8 _Pw_ /le5$^q%@k+'ϳ-lǎ5f,EԩSv%^#͜=NA_AyW8 |Oi#]44y 6`8%>% zMk ^+ qìTg<1GfSH-/ Bph?tF/ _9[kCWSs U |kG3 mQUN#*nIa[avS㍹E Me},H9J;^PѪB;N̆`Z3`rj`X9? vF{{3)aWHd]lj@ƏeHE~BY|oJ5J {WgEKhG-y>oxhx;?9O ϜǏQiy|zJQ|rcFeӉY~N"ϡS%;edz\z}&:|@@+W9֢?>quN'F+c45tl/N-_6촼h*}əR.o,CiB6ê7yWڻt7=㹈)%/]ALHpw2 vX*A:,2 \C&o]iCY;illo=K^8?^eq0U.nY7}JD k7*2^}osp@_~9U+%VXRy:275B_jD?k`"v,濲IaABExH\1ߙ!~*׆x%gb{;3YF@)(xk5>\J V $ fۻ,n 1*5)ܠi5fT̼Z& Yۊ |%Ԭ8h%k;!Oq.ڎg5S+TYlsixzשN\LxC NVa0#[.(˝9Žuӓ ~-X#;J-&n͢nϷ괕oj] E6yl f?0a-@憦l< C" w1*mu9 `g (쎜 Zq %y:G V[i3y587j= E#0=br^po_3x-V@+_:;pVZ[\QiP5[eWY*]\n%PcAѝoi1f3/ܐ&_fe:w#Gi rΉzr|m]v-6&4U`/r }/i#K>=ҞAjO3難|r;`' 檜-A OF0©yk*"t|f/yCDpVS㧿p7y>K\I}%졷^ҟ|j!g,1E- ޳_rF-|_ CcK9?gtV}+|ﴙC6ht9xS#Հ':-!'"jEk YR׃z-X|ıl/'|$/C,{v:FG~&X?0$|ISn]|h x{RzOvQDD?{ iיqPjdPr Eb^ 1=Q@69$Thx={soW$.lB#s8jp$[ wXb=hY"U Ew=(Qt)[1unm,6L[vh5vVVSB+9: [ng%jKCU@TL^oDnlIF6Op\&A;G6CA\p{<6huu5]enpfx井yB`stGBQ`\mk;E|S^N0*N N]7< 1ձ,d6Fm̤;`AݴV^nmGD- qy7|^'-}E=*Q^rCi n# V%/eWVx.P NЙk{oy^WQO̮Vc"tprqF:4vB~*}Ki^j0Kk2oaJ˨NlDžqI7Xk"'N`I,Yr-Ċ:l*s-B)I~/ XI2~W E`PSFS o,[D#N݅"Vs˄(լ).\BQ!-;M/eQ lS+s.Y }j'i#p%+vTp)pӌbZ b^cUuο__Pdv%+ YuZr=߻n(1=Y7-6>iy?4wl f Nw[W_cjyBi62kG sMc 7$h,I:|j26ciѹĀm̢qh H_1r 홻;#T+ua-P1䯶%fޔ# pXzw1-(?>NMT4B;92kt/w1`UB%;> 0)WE8+ۜ,;b`WB.wz eQɤ7Տpry(VXH#@-ݝW (/R]/cDU5Bz\h|k%PaPEMR{O;A B h[Cp`=8:ZWZT(5T=GbΖSZϫK#.08Yuk؈lClRbgy'Q{f,%#Ӧ٧p>L1f02O"ĕIriKɼ F]4^:ƛ]uCy^򮎊 ې0p>cpB:"K!1cSb[gGT 60r^s-{uT ( { d!=z◯=O܎i>rjN~ݕ u=fDmsf?3Sc  nYS}g97zUmkA]r'#r8^|R=J?|ܯm虴&vIѨGt/dvve{-L Bͭq)9΁蕮LzM v^B{< q_Ao zb~H AP,ېK;4.9a{DG6U\!duJQyU7UtF_cӥZn-/Ɋɻ%GE]Z#~ο__b&\ f R>(\?K8qd2pҟ~yw7]i%EN+u;`x"Ge &C^#$~f(,Ìj7i :0_-.`rd߮6f |;?ᢂ,2R؟4֏{RCg(׭ FT6}&7rFNdY =WM4_Ut ..o 6VZaiP]0 ʳ-M4~uZ]l #NX3j*^bR٣u*u,Uf0nBWf-cGZx_1k۠*47@+}X}1m>G{2qe)_oRS3pa?Y4Q4=nx+6֟ll(-øm%~ONjG:^[yb/oűSoV4Rl7˃[xeu`/M/Vk۵ ^3`!FZ7% I<7 blCS@%TIf`qxdR̓,6{wߌÃecfY?Y pp ľȵE/I$T)<:<~ƽW^3Í+|EX8z牶 @Jz_ne}b6-s(>KtE.jI!$z &fi7 _cthN$a!wPNjl ֐q@36{ut֬Vbzց(hTBMH\blr¡c_p//n +{&:q&y[GȒuH*@AhN. КawzPbO&ƈ?$xdee~`@P։"z52=9Ř-jmZ$ª!m#Q9yf&UAQ= $8[ 3dy0ɅDf\F+zxJLR5Pq ۽_qO93=qItdjK(%a6O3}lJ=ZcAr4! s*֝c5.릘H&78u)npd HvsSUf51Q0q96q\լd3PX>k% 6y!ewKu."7-bc"`۬Lzm&SEv ֹX)l_0y*`Y$)Ijz=U<ͧͭ2l}Rt=AF@ƵvWۦ^Kֹ+m4P`zx1(^0ęBo5sia c0m^jC.:[7m*>lX'm1 xu@ijA) {H:>^ lm΂CPT*~/=8%Y4̍;gr'~Ǐ%{J?$9&%ҸM>Iw7$෇IVrH'l }oz~;ϛ"xyTه8 k߭ҧ[e[5pק )eQzd}Ky.&0W* 됼WDa:REdg=ompja.I;3 xSbkWDpO)ͥ,K&ZS֦rA0z$4va# B?g]SdX,DT̖eq0N",o.`R( s8sUgaǕPGLye߁Vim(-svT9u΍h5S"uqiO]:Qxo. J 6PMh+h'\l9nX?z<*ZxPleqOEY ˊg=M1_JNw{}'oY{$}t/a$ 7y?-]AuI.UZvXk2W g=*=eB E  z^Z(A`SHH&NwBDAYsNB@}}kf֬Y3sR[?cH~ Ҳgz$4x&3z52m ~S!1{Ʒֱؖ{4V&-[)2yU|RVZPXx3?-?PaV uMhӓlћ}Z-*mӢN3;5QunWؼgU_JkrM+g|=1iq-3jUءNS9LyN k6/7װrzB?^o5lڳYj.U[}ФMn]JT fO6k%tn흷:T^=+'ԪS ;5j2)h]z5w^냘wԬ[B:里}ZowOzV-H};uRMIzuN=^JV!&g&Sj6hY亮+43nVo_un=Nfdž%vھIԪ=kVKJPUzftLZ~ZSfΕ3OhS褌Ju:$өMA71?A&ԾTIfo8jBlǞi_ {& ;?NV~]~~i|#+-Z֩87۟zXV*4l^;q՚×5[7F嚕۽n6رMR2uQY̴ެ49Ɖ1UӪټᇭSjSF*}Ԧkoo-!.WMܼefOظ[ժe4С;cgc[7M$kS#nj=:$|ھKZݨii=d&fJ9mG i j]%cBJj6=5Jr/uifFot#6y=_nX'vlr7r֕*UnؤGzJ7Z(i6WM4ISrj]kӦCi+wLkV;o6Rj]_i*fRi~nR?Hoʯb4z۷H,.)I쒞bI=fF$wIR:D$wKK4 -=-}DZNʊx5^5^55kv&* nfd;wl1KԮ)Isj2#%ėt NU_D'eoЭS21)Qkzr@-[6~uj_;}DĈCRfI~8\R^L(StIT-zfd&u.%3S]{g$wOJo:v鞤n/j$wNr)"g$ղg$GF.+F6L)}Gբk3IPe:f&Q<#tX47)(?ߩE b Un $%f—uJM|#gô.AOf4˚zE|%ԙ_*FI-Z4Ifta)6dj|:'$dp_5Z1vЄ$FcX8:B1rVC΁Jx|Qz~I<Dho_8DP:B oGTe g]Om"t(K{)QoEE\f &Cw;en CP"[ =I H+2]dʺ+C)vءm:iD3v\ηCۡKmg]GyЭnCwRx(5E!r(۲0OaQPbSbSsǶJ*v ;ᄝtN9a_8aT2.,U%%aXXsX<vESa3ϙGð3aaH%CzAaF62ihJ [-%Pr(q BZD۠Ű;e-LJaaNQ7~ܒaܥ_hAh |ZlaK Z@qR>t-ug̟",yeC6 ƨI>!$+~N"[,0;`!^d֛%d_h?:Gor >j ,pHF,Ɔs.#(!b#y`:|FsLJ8o<`M4&If~n.w#[V .Lc!/A2`9+Uj` ^ˣ蕭 ۈp|lہN`5اπA9p8d9@.') ųEp\:ׁMp??w_wv\>D^xھvOjA򪽯 p_3Z(e(e~y8r]a(`_L&W' ]dЯM3@0 gEb` \* `% X ߡgp/Ԇ P{е7qM@[v`v Swg pu09zXF3G#~Llc΍rF7o4i(eyN#ԆDŽn|8D/? AK,p8\@. =W5:o  B63p ~~@/c{}@?} p¡0ć#(䍖"p< '"p20 LCt3D̂syHGX,K2`9+V!Xvk#Fm:p R%u=R >ymwJfy34E{{XD9puH68*Wl>vC6C6Cvv8'N@Dl/N< Y_n W ɗN | S)UvJ Nibbbb¬S}R@lTlnHMV׺#@,"v ZY(댲_{C}^mt#?Y?3`=lu(`4C* Aqu?L&)(jQ8@iD,7? 8,u"}[!݇sr}[/`}e+)h5]k zHh3TփLmHPh7DGD :Hts9l fmh$˥X|Hvbl=':Atq֣ؗD9g$l,Ų.[=p \~7-63pf>RҬB`(0 FJObc!i<0L&ȘRiHM DfR$ Qfs<`>Xtb` KeJ?XV5Z`l6-'V`v;]n`/)|ρ r<(p Yˎ#<N_\ %pV)+sD_#<<]PzwDW]G-ʸO.p < @/<{}@??0 C0`80 FcZOg ȚL&\A >Lf3,`0B`XBeZ@ϯWJ`X끍&`3 l;.`7 | !s0pr\ 8 ') 4%5p|\ׁ[O.+px`Po<8z&K` 0 qtÁ3l$Qh` 0Y ,rB84hDIDM%F4hL,Yl9Ds'Z@hbs ReDˉV$ZEh ZuD6m$Dh 'D[m'v8zhݎB=F g{ wBvsy} \kD? #En&K~"u"Hq.V 1^ZxO{@[02 ̠F[nS.D{\Yg)o+JC[Md{ R׉n(Vvݱ-7н¾]{ݹ,xx"nw_މG_x4.Bn.-΅0e9}.s 2An~ 0`%p#aIBa޲u- &R>Pg`n DX c̡9*B_Θ@H DL9 6c0lAΰ6f Ef!ӵ%JtǠ%!Xcr|x~7Q Qzez )}4z;7x(wn+``KA hM6Btaz|*^B 3+oޤei#*rNxckV6uknw|Bc4i/y ɤ}s)^“OcUa^Iz1!` VOUZ6_yGSݢFcTWtǤǣHL)dL`,@]<<=^=Q_}1h~MS"ʺOG`U H*b<\ ]}4'p`BO~BOx4 'Vp+wZ OS^h-6ܣb:|(|p<=@x/YE%*O'}x}ϭi*K+'v?p˧+0Zoy >j_k֓swo_|;ғ5ϓkgn?JxW sw+zOM>zz$҂bV̪1YɬIt1Nh7lFSk3 x`k! a,}jN?df~hzZcFceO%L%N J 3 .3u:r?8) y(m6P׹E2-u_̤[-a]jf| + 2k 3#aM7'`7f #пv ݠ^l7 >e~4>C|;T>v92xd9QbXg1M -gMdOS`dǧM %_{oB.a} .i]4fsч.1 ;c ߛ+&~Į'3udLe7 4vom'"~6 t x دY7lv<=eX/ά7x^~ܻ%Xlx ^ΆWlx^Fװ7 l4x^Ƃ7ql[> ~/`3l1e^>˖eFrN뾂Ӳ-E_ii;gV/5Kl-;aY:^|m_c?Ml3}ɶoml;'3 vaw=+p/~ܛៃ~ٟgpZcnk(W9ܘW.7,W5>FzaJN`q nEk"'9)HĿOSVs~ X`F[yԁu[+ m _0Y/d;2`=Y.B˯kd\~, ?db E&oddYƭ2ҭjcw 3+dƻn8C~CQK0谷ˬ8u_OC?4>M򁈟}­V/B1T9NK|8i]}:(ã 1<84+&2^jO0<$Ó O1<4 GX3 ga(Yf},%{>b_e(1P2 R`1!bxX&b xX %hq /C8<^@gJ1Y23^mxZX tdl1L6EMa[g[[ך%m/ƚ'[Z(|v vڻi{b>bA\"HKirMo qȯOfsAÇ n䈉g1k8QSO>i0 N'U5Nu M͂-O[E1ag#Kuj}%{"ie1k8Q $g/س$+YCVF')LB^ N%Ϡ-iE&ϑɯ7do<ٛ9ȹcbܕh&/<ʺ.o]?hbZY~L*̭^5AAӶu?ٟ#ٟ}ϾEg갟p$~G"GwrdC!١Mgso`&P$-Ln Γv@&p?d7f/'9^i{|^ ʓn&[n6]*W^ex5x^ z׃/n6Ub*^5?MoE6[癬O o5v'#g#a=!7[4¨}ST쥨Qo USn=;ReE>TP EJ/:Da$vi_HR3#@62(kZSm\UÙCf:I}n&è3OU>^e۳PVf+А…p.VQX ւʣM&PhUї:w/̰NܠhoT&um7/[OY  dTeiS}*nu>ap'QF7`{e}袬Z.o!| +~D]l.a< %Uq)eI~B3=}o᫆TO>mz9cYu>~nQm'4g;Fws{E#bvU:#hlU]L_ fr~7 G3fo=T/z>_T_]=OIwO|0 }`=4{i[Y=PUrGkcdL@*A̡ZVt Qta 7 Fh?GPs_ S74`M4&Qz8;iLװڢ!G(eT Qwh$Rl Ǫ`8lUNTӓTd9E)7U?;M<7]{Al&fY*8Y*8l\a )k>CP)pB*@,FbRx&)Q\)e+rUX *lZ)L`RJyTXT**8멯qm@1F076c`M>ncT~ Emܩr bڥ,wV[+iA3{ϝOQ}T[Un11ٟ@- &0R%稦a#Ќ RIJn%YDㆉ jfP8\!N@3GfPv A NC3n8͸_*gfJ8TQW*o9ƾ%٣EOD E~@黶x]w.v ~P^v#n[0m Vu݁Y}~]fwaXG=\4尣F:>n:5C;8X1QG:X}t'pkfqWټsxT< ԐwQA~YibC P ņCf> Q[hm:F ,8q1n)nY1Qc$ede̥Te.#i,ӱQܧ e~FXP%, %G\NXg\e~NӪ|JXUIyPPHRnyX0XU%jXUiE\JbVehR[Z1]| w5`Sȫ ^-lj,M!m?:ɭ9ۂV'[=b;iQXYg mr=db {RKG-O+ ?P"ϡm֖G[G< <s[< ͏`'~̏"w=nl'XAM8vN$ͲDSi|AӰiF)qFo%M8KӨ"is4kKF5q@Ө.%3zN\$3R#.=/#3zAd0{2 +*ѫ_铺psa!yMVy hUuM?OuP֏*/nb[nc( (m9@G/bZ(=d.X.!QGgà= þF^z;"f}5(_S~1Uߠ S& 7ɔLM/ uT†9*FYBVJ{c1xɑNX%G9rk'X3Qk xT('&yij[$'X"';VC:h)KNr푛hʹt''g8~AI גפ+ʑ ƩLȜf,5߶Z9 l V[spPkkۄy)g[96ݡq%Mʚ|#h`-UZY`2 FͥX5m-s'җcŴ륭*Vcơ -8)+OYttttt]nA<W*{?^bi2v11ck>31~XcxX+c}XAc{#Ⱳqb+ױ{<*밲ikX*]U3[2"k&-Q)-&6R0&-#6=#}4UZE1vI0sҲ߲RjЊ;D5N~5)$;7kǛg'ɏNN[~evr>'CCҜ~椼Cɢ&Y='Ϝש&n_dvt'YNXŲ:Ydz9Y{H 1(PY)5P^Fyʓ,g\T90?+MB_/7zx+<|։ˍ?GaG+k ;Y89ɍ"Jav("b3T];ej7SEjT檶TwNO7YW J )˰NuEEgȋ|$˔sa!clOQ놘C xnu#Ǵ̍YA7evH+x.JȮ'72#DI͋)b1X7Ic~۶ qP=A4OF,P>r_k[؄^M~be$78N*!Dbn: +VJBI-x7u1lA˜[Ʋ e֡PO5Y^\Vf䅜|ZR>dOS/fyqY^]FM_:f+G !$JNIXJ8ܘ(rR yڎU;%tE斿 V| ]6H =#Nʊɱr2oP>ZIE3JP$ ccAgi1^Uܿw%!y( PTA.:D˨{NF󖍥2;'n.Y6XNLP8=)|cAQH5ڸ X`-*"^>sq=!(WN,_9>oz-?7\>K<+ī zYvɍȋdJ+!qeK&:Q,~'HP9f EBEs*AE&j̋JY iKr8&Hp,T VA@ǴwdQAY+&*Eb8uVA22oXL8N V~jzMH8m;[Qj`n3[IS)"o,o~"~GңHnLGz=]oO DKQ{ GT*(AQ D`#hBs2jԜtnbzk82 >(=F2b=̏ta,bZ~mjT+%NpS:eWזqvk߼s?Yv従Ҋbboe'.%)Eq2d}EJԉy +BB̔F~y.r7jE>Y)mdy)d'wlsƘՇp`3.Fvk4YpD;!䇸XQvĀ' mP4n(Ji׀rǝ`GqY~ըNN{6?3A?F[)=Er4OML'yNN(N,.I>Hv%ֱ=n*3<&[`U RHU-H% ieǣXD+Lb9YHbqy\\vtn;V+tfqFUpiPFϳb+Sc2^Y{CKqTW- Z:ɸe*>SB#6\A/ +o_s~Q }ϕ]qkkqn t- )\D흽Ğ묈ttv|%Պl\xrt"@'qTH 1ҒX~򴫨.YzIy񅕆AvVfeCkFJ'FQl*؃=Jzׅұ([ҵ3;9 <_In,J;i25jk&P?gнR$#C6v֎f] T^{mvObRFmw^gf*3"(;4lKJr}6MSVuP2 Ɔ&x dv5¥_ZZX/X/;V9mQ8Rޮfȸ5\G<ݱ&Ѹb#+^1=-pAmSx[b*HwP]De5ˍ9ޛLv{ 4lq5͊,`kg'K.̬q2'5:>6W]r[ū^bg?bXԩ=FHuQ1YQ+ܬ1*78ʨP!}WD/,#Qk[=!n{珞܉ ɁgbRWׅ(H[(>ܞn0y`"7d;I湮;Kؿ7 pF, kHGfZ߷"If$Zfy>6ws]o\Yyd`"_؏0ȇ&rӽׇTqPMAK΅A}6uyPj<$`)ɼyz!?ܫ>i{U4_ì g6,$XUQMzvhds3'Ŷu]Q[H&Š|bzyAV 4鸱n87~/Px8AJP0^^X,3dMk-"3&Zt~L4頙ITE#dYA|m[pFDZAᇗR,.R})2R+|)T;*GCÏ(e5M:([4ds@8#(~uwܛ"gLW>$;*tQJ5Cܗ1<"e/ۘrRHɅ_h_#P{1C}0%A_+ʓ,bߖ_P72ls>T۪V>jU^V)hi* b:Cf_ ]ρ4Uի.wg.WxSH!NHs k.$}Bж;fßȳ CtS,КZf!x,8wVN@g&JC8p"gz\89Ĵ؅y]FrfMntgEga扼Bސ(8 I΀A,+/?vN̏H8wkӎefV<'\s0R#ZZ,GjшcgUN 27q̋o;SZv9oLf㕞 ,;7=jױQ;Dœ91q$tA$)[{wO`Ydz0}%A93H L~A>tWWUWW+t3O+ՇT]Re$dxh d(slJi99RH&F90sQЯ0:VFڥ e UNsYYOSwbpwV-ԷYS{Dm)g1 S?g"bퟷRO3#1]]p ceNs&׮Hޕ o;[= ~~OdcHvF|h_㤾5,|rU>*PmEC .Jn4|7E 4iHo&4iHo&9qؙaw+쁮.V6iw\AcyK/[zZM@ ˆae402c02y]\, SʜH)-_vI5 %BL-Gg$Fc,!HR%WfJ8 }Om?!y|r w+>%٭lZz7yZ D3'Yᤡ<1Q/\sHa!sleTJ9wNN烢$ehSR>r`jǀɩߤum}H~S*4fK.Si0n,/!iMhYN}54BS~7g>'\>ϑi qޟD{z뜼מe3Jf6(9#KIoG4(_P*+xoB&ed {⿂8 哨i/>=Y4x*Yƿgt s!] Ce[,#Xz\o7_1& :Y@iBd{66xd$N7* ҧЈm0{^J6~7E( <4"̞ĐTH.Þi8C~Mj3!j | JCB\f |1g |,R)*)j?_!_фY7D:hkP왑b4J:#uwJ>9̯O PiBNiyR[ PL~4[IB$S$fAw#5;r ?#%QQDb:4SdNA>Ҡ+ ?B5j3&WHHMDD܃y@p_M3ltu!B;u&]%*g Mlt$ U,o%WFNJR@CR#g0šؚ̭|˚\[u=G2 گ$;ȓ|\{PN ᆅpBM`Mic,+"p E>$+VQ;!Gΰ."\㤗 I^fp O?-Q` ~9hTJi: 4BgNc#URjAF{@`{P2mRQ&vIehݰTtU0&|o袩XZN:jQ1N[)hr-3#bMd,$Q y Azf&¨8L!6L`( 6ᴞn&O)c3Ep BWBł\ELm : JlGmgK!}+;[fKV!oy rw [!kXl':9?M O@94^QW صWKՒZ"}K@Y {^OFJm,܉7Q*CIۣkrѫW/vѫW늘ɃD ѻOEt'>b ;*] W P_"&n h*5A?`sBПJ E`E޹w q8W̦h(ʤh0u#C;%Sz۩aoa1=QQܵsD]5ypKhJx 08\.<Ie|:*ԓjZC#}bVG5= =h>T3{G-Ws_h?k?~W'ڧ_ki?~|Zk?j~?i7jgWi(mQ'm5?Ҿ}Cڷ?Ѿ ;q'2L@_+L,BpZh^D/颽H]&Sb-ڷkz>&7 ڂL ;Df*chbY%d9ZI5 ҒN(ޓ4 *Z#xCioX2(Z_#xOS7+Ӣx[ijO=Mr?xϒY{Ns%EK2.(ދl\T-Ÿx/+۸xHnU?sj15:)LŮLxQoA|QsAae0OD6P"Q)YMI)z-|2:Ͱ+Vš)Zza @ҐwY=۩z5QoBg;hPpRY*"f2txqVO $@3ҩ}$5M ,Ȋ-1Bn C 8 g-ThG ٿi\Ӹ$ٌ*j+ ߤfT3X`C!nC D[ YBj.Oe4#?79` y8# }s4'r[bBDͮ<Zr嘝\̱Ǻq4angX 8 ptQ$7I>ΐ7/N DteO넪rki':OI}b]'x8m4OuR:g mf3N; Nnu!F_ B>U>9DBN?;H\$L~dȊ jn?2"b!/sɚ!ނr'I;[[(L)""U,H0o_x}ψ>G "\ aOkS;y@"pA9VCEMf} C$Ȣ;A!9du{>߿wLdwu;˜ u,  }VT̞1")OK zk^A /H(?0S56%\W\ya a@ 7s#Jw/;Tw2KJ IogTNآRKb줜dZzvJ#! f 0W7s-sY-3P3ʘ_Ydj4VCO"3% [|vωYsћY)xFY(vՊTW@c0yϱE*EFH%=]qWcmAbM%b-W)ī/Gkhgw!TT8Tp1<,bZ=>(c 76A#*(hD󈲋*!WtfWf 2UBnʭTHhF 4z%=֚*p Uns>~wǣqFs5.35ܮ5 ]]z;uyq}˧ 4#eJ4!S:O*Ld2|x~AE0!,Cĕc̒}^4Xpկ)Yh0戸fk%zp`]مqac绺.To+Y+I0vzE֕]2%0{TGh3/?}`BNX+{'zsuuDovG(?LG{:F'9xVaga 11zW23Wz\1MT==9߃[ʓԄظkZ{$fمi k#ͭplGs^2J~0SpeZz%MK *l^Z,W\Qְ0#6蝮6xf'<$_F` mX|.x%$-&&B)opB#xyF\f}}J}x,Y˲d6{6Gʶy)j?e6)["ȏa0 9Pb# *q=(lYu`AX 1=oat d,s#LղeXj|4;H銘Q`.Q->0>:뻇 0^ld%cK]=k$i?ׄP1$&D qn;؃w7UY`6R F-h͂# '5邏-M3HeJbC=IfV-|<҆9Nd2 VIgv*Mf1*ؓ (CQ?L„j01BTjE^߱doBG~] E %D4 /r`{C{(BϠ an@;;KNE"3!߫4"l uϞ?8w& ceIOMn 9fs'uu=ܒlșjŗ@CK\-GE-fi`6zd #&O̓@Ԍ!Obv<=..>WeY}o0v.CZLfLvV&ym9%8dM7e!|.wZfATehڭ+jFE ; i&S'=)1+h%= Q08{:ˮ(ޫYvU+`+kިo}סxAC<7MxySނ[KWo Vl`ߩ?sE`] O"z^2 1+s/lDŦ'4GILށ+p0<IJ.R_tu.bIpJRp}Xaa⇅I&XaA{ 4[_X˩*^12wF@6\fy BywGڇ}4P a6 K>yt!х >etaO 12 S܍H܉42Ǘ@$ t*M*-8褎Βcgnv gH|*Nv_ }>H=Oa2yd F)JٵARJH/ @~. R2nI%rIeQ%sկ;0ZHLN‡.fOf$RZ6<'׮8}EO'&sgr0/v=#x(hF;0hɣ 4;Ѹ`wH&RnܬSXK<Qdܬ Sbv*{S| İRjMs.3+e r a8aS`KDvR櫦\c,>D0A`tkZ4ޅ5Ry7)+pq%|j4|5WŋE zVJ40 /4rp^NM Ac*Rr4hj4-qRP [Nb PT5ZOS [˩<ădcpD v)40L2D&41\sThrFK qݣ@IO.뎁\YgׅE+[BE_ꖼ7]pѐ+jؘHg}8W{z-^VosQrdf5"7Zţ/MRE5 kCڢT4W)#i =ECP|5,c!)fVs^b4Sr"F{n& @mLWSo5duY%d>:AQ&KD4f`)]prRZZ༠HOfK.xT:%}+w$HP$oo`RPiwgM+Tj1Y{-I FأD-5QsDRHXrpn|qcy?ZwXM6Z1L"o3^y64N-.܎Pa}mvۙ݃v;CHf5 ҳS:]vvI=iY}9o9_-hoQa6 gb9nt`7LE=Ԕtҭd)k d:mV@(&٥oqQ?ɴaTRQvJr)oe+ܵJIKAY][ dT9$61yph]4)킳(}hwEhj٨B:&TOj/:ÞNcҌgg_/e% MN "-yǖ GWLַLi'i].KshCA4br2[9}^h6!D ̑% ,KҺL.I"<}y4.3MPe툃ǁ|yas2&)_vj6&B>"-> emY"}>1x\5 klF,nv28GNm3gkVR`h +hp pA*!GS\B3-gYpEp \m 5V)7[ImXw{8'_!c<sk[zWЋycp,N桏M桏ñD0 LN>%B7ifq,5Ca^L0 s\0d}AVde AUԀu`%XV5`-Xփ VcR7uK݌lA=}.8&pJ=z[^i=''A@D) Z@+8 ΀U R/z)ˠ \WA;:upm*Vw{}"¥vJ xB< t_P ƂqD0 LSR,OKѧêHa(a&f9`.`!X%`)XTJPA Xj PVZc*E_Zd=F l[V l;N `  4 8GR\1$8A hpp*R~9Eopk\7Mp w]pܷ Ew<O3/+oA(r[uG^ǺC &` `J 7p fYC@AFp@5~ pAA͠ 8 ΁.ˠ*[۹k8t n x:#<O3/+n[%v+JEX0`&` e`&j-5T}n*/5\|`X `rP*A5`9+@X VY%NפkaK]llA=}UTP\cVpAa@3h53T΃ <8\Avp--:pn OC 5OSgsX/y ^ x @QJ(c8Pƃ `"&)`*R0iJgs`57`!X%`)XTJPA Xj PVZI[ͥH[ ֙փ `#6-`+`'vz@8&ph~,M?cu B "h-gYpEp \m 5&em;<&w͘͘<A'x')x%x^7-E(G@ &` MKק,v3p(3,0s<0, ",K2T*P jrP V:du&]_c`6` ` vݠ{>`?]oI8`J8&pG1p'AR(])A hpp\AvpzG~=]C-pw=pA'x')x%xe:]p(@`,Jx0Ld0LtP f200,ʰdq$C_b (TjPZԁ`X ր`X6` `v֘wgc}!#(8$8A hJ>rp8. pk\7Mp w]p*>KyhJL)< tLP ƂqD0 LST0-zi>#p f` `X` X (TYTg5rXՂ\Z+*k:l&l[6;RvŤRo'`h "8Y4e3#<8 pAA͠ 8k \~>SzY}&faRgR8,,P*@%ՠ,`Uj]}e}V5`-Xc `#6-`+`'vz@pM08c88N 0SVpg9k/](yj,vp t n $<x x^x ހ Ei`,Jx0LZR355S~ajiJvfq 怹}{fO͂ݩYؗfL2TZSSWRS堶/]۝:jz6M`3m`{;N ` hAp48 Ԝ葚_ 0Xbx 4i48JMoԜ漙 "d2hWUYSWW<Ԁ;.Tj@'x'Tj3ꋾKG7-eɧ~x2@1 Ɓ0L$0LS40 l0|,b,@ P @5Am?KV+aa X ց`6` ` vݠ{>@AFp@8 5f''y8@D) Z@+8 ΀8. pk\7Mp w]p,Fx14c<OS </K o[b0%`<&I`2i`:(3@C__ӟr.|,b,@ P @5˭Wx+qXV5`-Xփ `#6-`+`5]ݱq}A#8C GQp '!!r8A hpp\Avp*~~K-pw=pA'x')x%x^7-J,@ҊqD0 LST0 L`(3,0s<0, ",`M]Cަı TԂjzlflJ6@%N.}M8{>#/!#(8$5<-\z+Mgp< ΁.ˠ \WA;:upmpܵJ7@%?iJ~c<sk]h !P ƂqZ$OOOՔLTe`&f9`.`!X%`)XTJP1/jA`X ր`X6` `v`  7 8cфapqpAa@3h48΂s<.K2hWUp7-pܱ@@>Y<Ɲ8<< tPV@1 Ɓ0L$0LS40 3羧σX,u!x.q)XTJPA Xj PVU`5Xւu`=6Zc=}{Xmff;;N ` 4 8( pqJ8  bͩX؜ 8 ΁.ˠ \WA;:upmpR￧?xO%v<OS </K o[Pf@1 Ɓ0LȲ/Y,}r6`J PfY`6y`>XE`1Xe `VWd镰\j A-XJ kZF l[V l;N ` YcӐau @8 8N B "h-gYpEp \m 5&n;.[S Kw?1xg9x^W5xނ.P4`(0L0LA)1:o>s> Va. ",K2T*P jrP VX% WWj5`X6` `v`  Vi鍃\A&pG1p'A@D) Z@+8 ΀8. pkXu үoX OW<< R һl.e `,Jx0Ld0LtP f203ڢggsyʔ2`X` X (TjPZ*.[_VzlflvnPRh]H2CI /$e/$me"36 FiglYZVq> )|;R!%IOf4YR[-!~D_TNI+nNc~V[}$}gт^=vؙ с8Tr$7Uhy,Z'*t e4Vl9IEC Qy0qt~WC><#/>XzV˂Oln|,OҒ<;e7-^΍ymbTmF_ApIhuQ}v¢sqYSvNyGkjxB;kXTyQdq8=;`$2wT3Iy%fv0Uь ].L~V0KGʗVE ݋4=3w8@G iBnYd[׈Sv\aDZ.Hv*nE1'isA֌r "Uc\'g9Ҋ}TuQ"DI`Is[M0['LnH[>?6a}>9 ӊif1Ea+Ӛ"ĊV! |^pV및]٧ϑ3k "y$]<ֽ_#eZ+!"hJԯ}.3ևKԐ]%* f6I}[oSؽ0_u_)(w|J$%{f-|)YȬZ!:t_y`1Zh kS#,G"h 1OWsF8[r9fKo:$$D4ou :z\)ܨ1Gu-l%ؙ2[zH3[m]l5߇v +Sh%cYxR-sت w< (2̒dڱ,d^aj(EðAŁfZ`1e?M7fӊD- U>;*,Y5Ĵ=l}JxW#h#CߐCsHo&?,Jo#(,{Y"AE)ȍSY\QB,fkSi GKEeK+%¯Fn%ܯyݱLS,m`;̯`ӳTZ~N$8;A;cꝨ7cꝤcꝬwcꝢcꝪB3a»_-gԂj3Ch Ra{尧!Fh\Z%T V62?A8:;+y`$`DAGt j\j[N`-+96AEPD,mH SnNQ vGEY_GDBKkTW*#auc{͇ίVcD]`[Sn*!\i}},Y3RQL^Pzg`TTM4f٪&UUsT\USw9yww% TBUK2Eb,R0UUK3ޥn,UT-XzrUg U`TJUhT*UnTjUaTUQzUoQzW׍NվaԩޕMc]jlRU[jջFXzתڿ3֪ռLǖJqOm#ש|4YZ4 I#8qq+s8⸭lx s,\99896wbG;1c:"gX!gQ6=j|)ѽXӎ~VKFyۀAϩo|Yx%Yd/Q{ )mvH2JsR_R vS2k,#YH bPߢzRߪzQF6M&4ߣuZGhU͗O(آUP l=ͽydg^֠=`N|tOf:d(A1`+ib)糾0؝ ,3ڑBa1 ~ciQ H>ZBbIk(uXsTANآj|u*H7 5n l,jtW490Vi :S4*/.-Hir(5c0uI2ϙa;X~;)_TE_Z!ҵ.r1ԧZM[NU0vRBK(N0直ڕ_ݥEO^X칤g;Kzv\kpeT}:VDc#VEMQ(Zs^]3˺gxh*Z_lŐ:s zvFW쌭[ճ9Yq]`l-)L=S%MsP [W i}i(j(sskv"+(/)@+8|ɁQ溜brg]vR5vj{˺Zu99&]ph)B ㍨?7YjGDge\bavQ5KjlW5ɨW{G^ջOc۬H.ӳ{*Oŀ,cI'&@4ڊ7$~N#U{sPRM&{sX!=j=F1{\E5ΦGS/}2'ռ ~_t"Za?o6С#e'6r[;*{(=?CoYukh0\m^#'~,znyM0 Ļw:UXwgw;3;ֳ[:kYp#.6O*}l>#1m碨!Q`TJ0C~2fJ6񋘒,iI4H:Yl'J3[*މ>caZ_̧Fز>՘â6Em17]7Y%2Z|D+ϑÓl'g08GjsŻfRsK0ťhѻLqq%2Ko}xO]oq<Ü2x NjC~ܭ8)x9$8∓Mq7>K6S^q).+8^2eqIf+N4eWq%̔geoq< gN{'ܭ8+})*p]q$ 48:qĹLq3SGd08^,8/+.7433Gh+q$(uϪx9{Sn=9-N Żx#eG`ZqS/b8SNjC8d33S7'e78o">G`T+oev̮úzTh8=2c) Ma*o~SGq8=%1BNT4(C~Z:3\!o;^J>,<9sA[۽,'-13W6,YRwg "Zn.ezj oV& ^6FC>1 57K7e|[yG%yg|+As@s#d E} R5cͩX/ o$R$Mh="wMпv(7|c`;%d[%>L1N]r*Ch80}v`n/ȏЬ΃3̿ MpbuR-tD΅ׂzMe-{)|ּ&?B'"Ts!9\'E ډwƜw2gZyD]kzђ!՚˱G7,CglK[z)ݙϚ 2υV7;a,{ybW-TXQ[L|SJ|SXq̙z7惖ee[_Kԃ1 U#-U՛$UVqڴK%X.)2t2 W@oY>C<Fȇl~j&BG!H.+QEodfe)S_VŻ_s n tl&l#=i+f>i_2淬6lTM"hczMlhUJa{ldR~0߯GBV!5*FDBOQYIx: 7wyK9D歬: Cv0c1S1)NJ,M8FX\I!ѓӸ-[\}/bXA}DsRJvIJD䥞:ŔخXADa#UK4 ^>gtEpJb 4_S2g"$1"Ǟ뎲RwGD<( kݙaytvZ,Zb+ ᙬO4M ,CH*{lwf}bg5> ՀGb]cʍjN(S}1v3TB&^%]>'dp! Km5>^{cteM(r A2CP i?k-{p4=T AHW<q:S1PAFjx'тvS>/\Ac(.پ^'y$[}.A <,yn{t4ZnadftnJHvCC E-e04f7;a;t5geIb3O#N(U>Ķ6fzL3-A%6JSoa0Tsvq'f4Ӗ8.%EE}-ٛU{$3-\ .@Il66QMԆmM6o6"M7I4 W*Zյ4 1.V؉̕wGdU=oyUO3*U`ɸ.,g6WTa}Z TfGCoɿUY2$_}u( |\e.Ũvb/p?Ϟ;n^" oy.lGXec*6 6Q3Ҟ-. y ٟt|"y-a"WؿYm0{)7-'G^aӪ|eQG?h9%t5._IqܪLR[諨p;wLj_}e 5W"P&Ɏym`=F=_T;HڍIVÝMx£b()ˊri7sgI&iڕKzm,ZIҜ;$tRYzʯ,8Eg~A5\w:lS'ž[zD tPKF'ikH`R.()2Q.~sQDx͛F/q+rE\\XFA2QrEKrE)ChE0:X/l(36Ծ_̐_:B .m͸ eS= w/ܘ6rv5M6TpVC=Lv%žgyv92(2RR.˴TCs'K9Mm=,)NTc/[!Ot~4q muRḶ>ۓt< "Z~Fc!DGsH^^D.. .qVr "0S 3er4hz|refZaN(va XtЩ]S %'t,ht69^vUJ*v37"A7z-V!A;ȱ29&ccX(Z>TȫPy!r I*a`ICyw|AhBAGdNB% CvFMaE;mS@Vٞqetpq@Ҥtvŧ%Ґ 6sڦdEG+Q4FsnWD%-=Yɛ5)n8 bkь͆'KPiQs[̦X(&jt=wlpl4˭mdOF1 q/|-jEHd1]B(ʤj7DM)' I/p1+=H/p) fZj͇`N_v :/Q’y*EQ{AT1]gah7֕mc/ /~FU-2pp9Eup|P |siq5>هR:E~GpaisߍuOy| *!oS~W9 9ݏ:.?osU@j̞i#Z~Qnਏ@*ǵM%m\vY3 S2y{QxYaqQD`9.xϏ׉ N L>SXuT8V ӂ82M!+Ăګb sB-p̚(PE b $}pAsXT`eÑo{/b@4w,8wE#"TP8zT⠠ T O05j?c6F| p͠\찦DȢxĪ-xĩՑ;hux$,Ghx";G#K>=H{㑾GGY? uvbN Prgyf&j1Z*,miD>3`:'Æ^ 1_5H S1ž8@rÏheL& \DY6imB}b&]S!eC|NIsO.ΘA&u<׍ 7kIJکJ !(}p`@F8h'+% OMu']S8VXK}kP{EŨ)T^i.-{bL"Ў.pE{cx''<4,l6!t=6N!̍zP\8vh {0ly H :Mzf7eYwl8P ፎAQ,US[)#_-pc{7&`{P?lysj8&1R?&ПBz7D ١QGY:h[}yce&܌eÏ-,@. wӋP~Cet9B! g Zd<&<2*El 0f`4d33ܨ]_CEU~Y):'*sDm&*MW܃k|;G 3]cBW/Ll â ڥn&;5O氝@Wr[z76pIK s;7C>X-:EO(ҷVgIO=4mwcCd`Ȕ F0rCVw=_ 2{kFy Nq 7Ẍ́2܂T؍|e$h'q3,K0Lΰ+;"i)XzF"S|b~6OBaO&A@/~\[{SZ{c{+iKH_H'K"; ,;p$mvܨf*,8?yKMjAଃo`x] Py?ѩx"pi)mLIfV-mD槄 ؗU"vT"v*,aypoV w j3c\n'H]UaHRݝ̇Ncj&;^wn$8٩s˧ f14VqqB{>JLS[E؀s!`6RɞʼnSQrִI(F@TTM@ Qn9k9:w8'T;J5 Aeb.}(b @6nk>5-J{Ida{lO&6 ystGJIfeNԼ?U!`[vMDmSsD" XT6E@JJ+s8TQ+3IGfM HxL`]TG0' 0_gx%/?l>6 3)q[TE!FanLƻ*_*󦎑" Z7U&-ID +4ϧpGd0q$B9ZXQbXSD2 W? 4bȳa 8'M@Ppł \Jq)VHr{-nE_N6,R 9f9aYK8Nl14[ =mc6$EH'濐mn.&l`k9(-0 Ikb<\Xzcb*"NV X3fڟdD^'&#Z"jw`fHȖ.{e vFkxݠ'y}P(I΄Saur'45nqd(-P9Gwb|"q̆h-atfk*D;bѼ)xw@3jy2ʎ X03$΋]&  a(F'ӧPH. `c%zcdKI3p+K,Fp5`adpqǨ\Sݳ0XL32xX=z1ATTiU L<‚r|<[:>^5t3JԆymknñdw2^, (/p}`WdXAQ]p[|\ N]M ܰ4PT.W`?_w0|!7˔DmF$mD ^Ht1kr`n 1%%#4() PNX~UwVf+\X.C^x`* D1/L ԪapFDz6ov{aSfGH 3R/3 \|<% ';d3-sx6_ıGGꮪu]P7'\ =αJLm^R% 5 r\uطX@õꞋ>5|{!8ְ?VR,?PսKb -^Xkī{-kսK.Rz yu;b˼ ^ݮXjpWf,u0ѫ{;5\սK]5\퍥RWy bI^dXjpW?ڃov-.론]WV];)^3pm/܆P\r"4bPx{mS2:m[ӲmW4֔4fBc7 i4RW!ò~a֮Gw_ѿ Wzi>M|{lA_(LDž' j, x4mg-!L$/ F͏/05B(!ш":ߵ񁊖Sĩ iUE òhc+뮩I'j-Df Tk`r0[ua8/馫S1HG3QŊ")7c8G?vUF?ܒё8;8͏i@;FMC=ihrHr(EzC2t\ۙT[@0K05Td;GhnmSlҭZSetEcLc XC1P~ey9]0cd(DrF~:Y1z;r/}\wݼ"]A'0 Px:y>D_c'Ƶʼn1Luթ59ܬ}ܩ9OBpl󝢢MN&ZHO9˯ Nj+.$qGP;kLlJ<֯a+,e 2 c17+GqX[f<<6`7Yqxw`%tPX֋gJ$=JоrvfSF ={_)4zԉi&AbOĹONM@E|= ,eÏ\Tw5d N apkl'4Gl'5fu(Z=W"|y2|LTD 2E?P)~l·O9YӿOqI Q vAS oa4a2V}֙6R>BT'?{/(8a`lA/)ڂђA=-?Wڌ˨O2 Hj<*4owh-0{ӎAu|Il ǁm(cͯ9"C̍N͠/b'i~aS;OFc:A->CTZo/osPQ]} PI7'L[Sl0h!NX[4 %.{VQP %xxU&mW/E^㖠3u,_ǴI>I+q' )>'7ğIR6c=}c>@Z]ܞgn0gwhd@)iwX\hS*l=0nv]6=1r}.6ehPDPmm{ 稭 ęn=f\3ԀIeyS':ECd% ߆,7n ^~ 37Q˞Ѻk)^Ԭ1t.O}xt6=a#5|HZsFTX2ŵ1ɝ[=Ԅs=} PEXWei~.NioJOAKżvފ;7tOҙ'?ػjt`ߴ2؛])u)-^>W 4C=?bxJuC{[^gwiSWYJ׼,&RWR2gLp3ZӨ~[z=Sw1WKݲGƻfi.Z,w]۶O}Ԏ<qcuhpEj|w67ꕸƼ#ދ`=:uҽW>sK6txxyۅB.  2*ɤ?OHG`V' z y - )F!,,_ɯ?ȡUB9@ [_~/w w)7UBu_#d¿R><{~ OB$3':}f|M}y/r|VZV!!G y _R!/?+Ÿ-8o u&!(")Oƿ\ȟ<MH-yl>&ۅ yc,o_H3B~_,B_녿 oXY' <οLȊWȳK_wVP)dBw9Rտ-Oc!dN_dDV/ou{'y~F)_}.2.?+&%kSȣŸ%K|nWV 25ngt{ c ¸nLbn۸ 8ڽP%ĠV(Qߡ25yyKv@I/M]PԇM`MJ(Ⱥ7=JzVӿFPCgDϦ:N"dI~קUJ_L}>[FVN]~9Oj.Tz.V-B]"x=T'63ZeԥBX]&yj?Q 5Q륞pDt=7OvV;s/~?~a9N߁A}94'k¦;q>+-+:!s[LX࿃ -omw wcLlԺ;#u.QԺ$E.mjlldU#xg(f[ U۞(,PL`܃ ;^ x%w:yx*gD}9|_VpMkƗ];,'C[ڨI>AO_]E{fo&oU(-oPV}@84Da9=tUPO5[$ B#+IB4@ά"90Ai'98b+YP 3&u9|Z}wCOt\[G67ndc f>DZo '  L#Բ-zQF+O;Qݎөؙ +NO~{O|=( 9r ;Qpm(Љ($bvO-%g` uP,6u"v@7,HSb-gS*M0HeҔeRK&U2A%rT۽;hB)pλyXtP '2/r ( N0}"w 縔ppfrL- sZ(?w\Ԇ;;=P+ylS%K#jEx j &#$ G!1ǐGɘCx^QV (;QLA&gvQ;xj-C4A:Id= j*YB8j)%)" GYpc=\"Ia]PB3Ex wy)tQnANy4=#&E, !z^jB^@89P \г's \\l 3G%N տ ̻eڳ796+#Ϲvj9g2J4<Ģh4duln.$6_ϗG7x}f_xgt~l᳾0ʳ<FԠ>5TL12}wPqKN+/F;f,b|؈4<O,4.Ѿ8o^bGLOcG߱.dcTQ)0ɇIͲ3:p'mƙK&6F6C^<159?m\}dɗ"tI#j3JUuUON~?eێZѿCp-q &s`԰U7w62MߍYP% GR(ؼB.^y~y\IJ ^JJ ɋVj1Wf𿡲 Q܎oTb[C;?0zQAU~v7Y= +E ~`ԙ-P/$JcJ pA ҚWLSZCLzՃ#xAA=ORL:y`S6{} /&̨(hzi7%HmS8yg>{-ZE/؛ 2ax]ŬM;dXo7 2/4$P\U5ǍAbG D"q88+GB&gDC_>`<멸2t . ]eeWIcXŅWQ7.E -7BZx~pT&ʄ1+Ƭbh- `/EET)p G('|Jr|$H>A A8@ H- D

.?!.O+X'4Ri|Z@+CJUx1-/#Ed3z,{ m`6;G$·Nhl,.t}W^GDΡ w{jMjP KPo}*6vSjпֿѿ=w`na/P;%nzzK/TP{H/= wOP/o}Pn>jPZ,=^K,}P-}PZ2KM/zP[jԕW5R-M-5W =IBKOjj u jkҐY: ᛥfZ>K_/,K`YB ԍm,}YK-fvKͱ\K$BuXҐYY:-tBsoZh[-RYjntНBKBa;-@%^(Be-Hҷ XۅXNYzP[jnVXz>VZz \U^!jKBzPZzkz P[a#B?*cB=jDž^+:I APYqKg yzBV}7 i6-M ~Q藄~Y_UY*o,қioK=mi3dׄzRYyK_ԋ&tozP/YzP/[KUK ˄7liKfJROni;B+bK jm&KoiBxK}` `Oh4cK[@8'[WY[եdiB>2[ SYlu& []il=Rl5VWۚO' SluzZ[gY`OolB5~fj,[`mM\0V7ٚxf[b6K[8"mkbKwتVsl5]U &vgk.[ͷ[-m^li;ބm[e黉wluZ [n;lu[e_lMA ؚX`}&/Zn&F &?jZeն~`ObmZa[#~R1[=nkzKKz欥s&[-MHiKem7mShn[{l&[=klzV/ښ /Ye[&jWmMBobW ×`yV@q( O9OrbhCG@?_gB9 !OB?get(g -WX{t/Pc?B~_%Br -Y\J Xg7P2@/ɑ[<Vn-ܿ|/(gAZS`;`ª+e;ғ'R]Q.?#dS'\|c(K`HsaC>Zp\n#QlazJ9J <eB=|ý()qg҂bBĭЃy%k`f67 F8ii'.ӽovąԇncjs&11.qב[q<BX4b#&䁻> TEQ~O^ DL}m7[1a!6N3& ׂCo_l}1ry}JXQ_`D'GZ .zRgL Q+1 `)"<|,1W]B1Gt1lDy C!W'?|0hV'b厠 Uy U:qװÔyYhؤo`O4G(_4$A+1gaiFc[hfp r x4 4Ghj$>yog z >~Ƙ :tuSC?נt*w~WdaE.$0'#2~x4;+'!Dsa^h:!1PdG1h:8FLHaM8 5o/z',&ca|  cĞ\5H%37o1G?c0H>Ǭ8qLj ؈mMh4K>0W B<C+ }Ę4$L ALwL"p͚ #XUo0S5^ÃЪ될h<*>Ecq4&ht*J42Zq uD&3̐7m:Nx9b+i4Gp&qS mP]Lz75I_Ť~!S= <Үaʦ)DKl .-L("4G ce3MrD?ƜclbUoy2$SWBLU5閟׀\fHjw#zƘ=afVd2mg,ŅIf&1;T?|Jis4%<%>uUq7y.+)$r2)n )ZXښ3X;X8k5zs^3e7XB3:ɩ<+"OzB"Iiqy5Vtټo#1aƱ|QxjWqnҦIf&Df|ǜ xȔ,KLo<ɌNrOs8:D[d2l6&j٤?2Cj7 Nb/8ln"/)&]LҶ1i Q uxetUpI)31#qlbc>`7tN  4̇XYjXYqe/dldb3ߚfB)gJ#f/f2I"I:&ʛrb=j?kv#ٜqabc~~`Yu{IΦT*m4v  \Y78q>9H>9=E-#OzF"2CtVTAz3w1'2ԤQmjٛΰf23vEԜ-Mab1瘵/dIf*9&8LY0 9ƦʙJm<&3!έkRx3X᜴Lx`"3q_398;;Xjc&qM"2%a+SM*54ںԸ}e`bO`OCZIiJ3),ػmԤhS>M11)$LY32LY8Yc9WNE" )ѿ)l?g,̇PX#N?hHw3yb+w$.h=;4IfzIgf2Ĕf-Uͭ Nymz«;NVf^H>$bl3Ld5!f(|MJT4o3zŢ^b&3L9o3sK*rVqX1U1MR^ pL55V9&D-#oY1&,ǘk9_1mҺ g3;Kkj*)8ܝv4WӤQSn;Nf t:쑷қ }-7-||6oʥ)<&aqf.yZi>WQ"1;33y90K灈PN9|`weM{tawyM4/Xoo8\E,d!J^>w4a?2Yt|L7iȔK!3ae3LAU2ԔP3삪,<*Y'uvt,&1ϸzu߇ZL8G̯[wQY1Y>|'d9'e9˧d9˧e9gdxq#<O|$'x@< I $Ox'< ēIO$'x“P< I($OBx'!< œIOB$'x“Px'<ē|O>'xO<iO4x@< i 4Ox< iOC4xP< i(4OCx!< iOC4xH#x|x|<>G<>%<-i OK񴄧xZR<-i)OKxZ%<- x|/O􁧏xG<}#>Ox<}> O_􅧯xW<}+O_x/<} O_􃧟xO<'~O?x<~O?􃧟x_p>p>p>p>p>p>p>p>p p p p p p p p p p! p! p! p! p! p! p! p! p! pppppppppp1p1p1p1p1p1p1p1p1p Kp Kp Kp Kp Kp Kp Kp Kp Kp)Kp)Kp)Kp)Kp)Kp)Kp)Kp)Kp)Kp˔p˔p˔p˔p˔p˔p˔p˔p˔p9˕p9˕p9˕p9˕p9˕p9˕p9˕p9˕p9˕p+p+p+p+p+p+p+p+p+p%+p%+p%+p%+p%+p%+p%+p%+p%+pppppppppp5p5p5p5p5p5p5p5p5p kp kp kp kp kp kp kp kp kp-kp-kp-kp-kp-kp-kp-kp-kp-kpppppppppp=p=p=p=p=p=p=p=p=pppppppppp#p#p#p#p#p#p#p#p#pppppppppp3p3p3p3p3p3p3p3p3p [p [p [p [p [p [p [p [p [p+[p+[p+[p+[p+[p+[p+[p+[p+[p۔p۔p۔p۔p۔p۔p۔p۔p۔p;ەp;ەp;ەp;ەp;ەp;ەp;ەp;ەp;ەp;p;p;p;p;p;p;p;p;p';p';p';p';p';p';p';p';p';pppppppppp7p7p7p7p7p7p7p7p7p{p{p{p{p{p{p{p{p{p/{p/{p/{p/{p/{p/{p/{p/{p/{pppppppppp?p?p?p?p?p?p?p?p?         000000000GGGGGGGGG(G(G(G(G(G(G(G(G(Gǔǔǔǔǔǔǔǔǔ8Ǖ8Ǖ8Ǖ8Ǖ8Ǖ8Ǖ8Ǖ8Ǖ8Ǖ'''''''''$'$'$'$'$'$'$'$'$'444444444 g g g g g g g g g,g,g,g,g,g,g,g,g,gПB%_ )WB ПB%_ )WB ПB% 0@ ( P 0% 0@ ( P 0% 0@ Sx^ Sx^ Sx^ Sx^ Sx^ Sx^ Sx^ Sx^ Sx^ /PxA /PxA /PxA /PxA /PxA /PxA /PxA /PxA /PxA ) T@ 0@% 0P ) T@ 0@% 0P ) T 0 % 0H ( R 0 % 0H ( R 0`% 0X ) V` 0`% 0X ) V` 0`% 0D C( Q C0% 0D C( Q C0% 0D C) UP C0P% 0T C) UP C0P% 0T C) U0 Ô00% 0L ( S0 Ô00% 0L ( S0 Ô"""""""""0p% 0\ ) Wp Õ0p% 0\ ) Wp Õ0p%D%%D%%D%%D%%D%%D%%D%%D%%D%%Le%Le%Le%Le%Le%Le%Le%Le%Le%B%B%B%B%B%B%B%B%B%JU%JU%JU%JU%JU%JU%JU%JU%JU%F5%F5%F5%F5%F5%F5%F5%F5%F5%Nu%Nu%Nu%Nu%Nu%Nu%Nu%Nu%Nu%A %A %A %A %A %A %A %A %A %IM%IM%IM%IM%IM%IM%IM%IM%IM%E-%E-%E-%E-%E-%E-%E-%E-%E-%Mm%Mm%Mm%Mm%Mm%Mm%Mm%Mm%Mm%C%C%C%C%C%C%C%C%C%K]%K]%K]%K]%K]%K]%K]%K]%K]%G=%G=%G=%G=%G=%G=%G=%G=%G=%O}%O}%O}%O}%O}%O}%O}%O}%O}%|@%|@%|@%|@%|@%|@%|@%|@%|@%|HC%|HC%|HC%|HC%|HC%|HC%|HC%|HC%|HC%|D#%|D#%|D#%|D#%|D#%|D#%|D#%|D#%|D#%|Lc%|Lc%|Lc%|Lc%|Lc%|Lc%|Lc%|Lc%|Lc%|B%|B%|B%|B%|B%|B%|B%|B%|B%|JS%|JS%|JS%|JS%|JS%|JS%|JS%|JS%|JS%|F3%|F3%|F3%|F3%|F3%|F3%|F3%|F3%|F3%|Ns%|Ns%|Ns%|Ns%|Ns%|Ns%|Ns%|Ns%|Ns%|A %|A %|A %|A %|A %|A %|A %|A %|A %|IK%|IK%|IK%|IK%|IK%|IK%|IK%|IK%|IK%|E+%|E+%|E+%|E+%|E+%|E+%|E+%|E+%|E+%|Mk%|Mk%|Mk%|Mk%|Mk%|Mk%|Mk%|Mk%|Mk%|C%|C%|C%|C%|C%|C%|C%|C%|C%|K[%|K[%|K[%|K[%|K[%|K[%|K[%|K[%|K[%|G;%|G;%|G;%|G;%|G;%|G;%|G;%|G;%|G;%|O{%|O{%|O{%|O{%|O{%|O{%|O{%|O{%|O{%@%@%@%@%@%@%@%@%@%HG%HG%HG%HG%HG%HG%HG%HG%HG%D'%D'%D'%D'%D'%D'%D'%D'%D'%Lg%Lg%Lg%Lg%Lg%Lg%Lg%Lg%Lg%B%B%B%B%B%B%B%B%B%JW%JW%JW%JW%JW%JW%JW%JW%JW%F7%F7%F7%F7%F7%F7%F7%F7%F7%Nw%Nw%Nw%Nw%Nw%Nw%Nw%Nw%Nw%A%A%A%A%A%A%A%A%A%IO%IO%IO%IO%IO%IO%IO%IO%IO%E/%E/%E/%E/%E/%E/%E/%E/%E/%Mo%Mo%Mo%Mo%Mo%Mo%Mo%Mo%MaTwkUc; u ut9k~Enl(NXveg,;˲Yb*6,d.eWYvò,r YvDz{r%6''Pp*v<zgw9b=SUMO*$tG<4 k4Nx`0i6Dm~锃2)e1$fl! 2 J5 o.Ώ6و&C".Z-]]njj=1%QXݢENJ+D)!cD%m[TsֻĉKf%zKTsv琈߂GphyXQ ?ZC!?mT#wqjW#hb'28m637s3?CGyqZoR)b"~d9~9~VW?~,Q+,ͮvvXnf9~9~\VW?~\VFXv8-f7-Gu<~bsή~?'_؟W"fqfnO,STv۾svsĉZ_؟W_Q7Dџ"3e}[$l*dO"@~B {?qVDlO]?!}|vae~ {?VXo55k\ì[sܰi5y\Q]m.ȒQ09O?43$Y]lסK69խU'аo]~ѲCWf/Rgevent-socketio-0.3.6/tests/jstests/static/qunit.css0000644000175000017500000001064312261120545023462 0ustar sonteksontek00000000000000/** * QUnit v1.10.0pre - A JavaScript Unit Testing Framework * * http://qunitjs.com * * Copyright 2012 jQuery Foundation and other contributors * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license */ /** Font Family and Sizes */ #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; } #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } #qunit-tests { font-size: smaller; } /** Resets */ #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { margin: 0; padding: 0; } /** Header */ #qunit-header { padding: 0.5em 0 0.5em 1em; color: #8699a4; background-color: #0d3349; font-size: 1.5em; line-height: 1em; font-weight: normal; border-radius: 5px 5px 0 0; -moz-border-radius: 5px 5px 0 0; -webkit-border-top-right-radius: 5px; -webkit-border-top-left-radius: 5px; } #qunit-header a { text-decoration: none; color: #c2ccd1; } #qunit-header a:hover, #qunit-header a:focus { color: #fff; } #qunit-testrunner-toolbar label { display: inline-block; padding: 0 .5em 0 .1em; } #qunit-banner { height: 5px; } #qunit-testrunner-toolbar { padding: 0.5em 0 0.5em 2em; color: #5E740B; background-color: #eee; } #qunit-userAgent { padding: 0.5em 0 0.5em 2.5em; background-color: #2b81af; color: #fff; text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; } /** Tests: Pass/Fail */ #qunit-tests { list-style-position: inside; } #qunit-tests li { padding: 0.4em 0.5em 0.4em 2.5em; border-bottom: 1px solid #fff; list-style-position: inside; } #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { display: none; } #qunit-tests li strong { cursor: pointer; } #qunit-tests li a { padding: 0.5em; color: #c2ccd1; text-decoration: none; } #qunit-tests li a:hover, #qunit-tests li a:focus { color: #000; } #qunit-tests ol { margin-top: 0.5em; padding: 0.5em; background-color: #fff; border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px; } #qunit-tests table { border-collapse: collapse; margin-top: .2em; } #qunit-tests th { text-align: right; vertical-align: top; padding: 0 .5em 0 0; } #qunit-tests td { vertical-align: top; } #qunit-tests pre { margin: 0; white-space: pre-wrap; word-wrap: break-word; } #qunit-tests del { background-color: #e0f2be; color: #374e0c; text-decoration: none; } #qunit-tests ins { background-color: #ffcaca; color: #500; text-decoration: none; } /*** Test Counts */ #qunit-tests b.counts { color: black; } #qunit-tests b.passed { color: #5E740B; } #qunit-tests b.failed { color: #710909; } #qunit-tests li li { padding: 5px; background-color: #fff; border-bottom: none; list-style-position: inside; } /*** Passing Styles */ #qunit-tests li li.pass { color: #3c510c; background-color: #fff; border-left: 10px solid #C6E746; } #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } #qunit-tests .pass .test-name { color: #366097; } #qunit-tests .pass .test-actual, #qunit-tests .pass .test-expected { color: #999999; } #qunit-banner.qunit-pass { background-color: #C6E746; } /*** Failing Styles */ #qunit-tests li li.fail { color: #710909; background-color: #fff; border-left: 10px solid #EE5757; white-space: pre; } #qunit-tests > li:last-child { border-radius: 0 0 5px 5px; -moz-border-radius: 0 0 5px 5px; -webkit-border-bottom-right-radius: 5px; -webkit-border-bottom-left-radius: 5px; } #qunit-tests .fail { color: #000000; background-color: #EE5757; } #qunit-tests .fail .test-name, #qunit-tests .fail .module-name { color: #000000; } #qunit-tests .fail .test-actual { color: #EE5757; } #qunit-tests .fail .test-expected { color: green; } #qunit-banner.qunit-fail { background-color: #EE5757; } /** Result */ #qunit-testresult { padding: 0.5em 0.5em 0.5em 2.5em; color: #2b81af; background-color: #D2E0E6; border-bottom: 1px solid white; } #qunit-testresult .module-name { font-weight: bold; } /** Fixture */ #qunit-fixture { position: absolute; top: -10000px; left: -10000px; width: 1000px; height: 1000px; } gevent-socketio-0.3.6/tests/jstests/static/qunit.js0000644000175000017500000014166512261120545023317 0ustar sonteksontek00000000000000/** * QUnit v1.10.0pre - A JavaScript Unit Testing Framework * * http://qunitjs.com * * Copyright 2012 jQuery Foundation and other contributors * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license */ (function( window ) { var QUnit, config, onErrorFnPrev, testId = 0, fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty, // Keep a local reference to Date (GH-283) Date = window.Date, defined = { setTimeout: typeof window.setTimeout !== "undefined", sessionStorage: (function() { var x = "qunit-test-string"; try { sessionStorage.setItem( x, x ); sessionStorage.removeItem( x ); return true; } catch( e ) { return false; } }()) }; function Test( settings ) { extend( this, settings ); this.assertions = []; this.testNumber = ++Test.count; } Test.count = 0; Test.prototype = { init: function() { var a, b, li, tests = id( "qunit-tests" ); if ( tests ) { b = document.createElement( "strong" ); b.innerHTML = this.name; // `a` initialized at top of scope a = document.createElement( "a" ); a.innerHTML = "Rerun"; a.href = QUnit.url({ testNumber: this.testNumber }); li = document.createElement( "li" ); li.appendChild( b ); li.appendChild( a ); li.className = "running"; li.id = this.id = "qunit-test-output" + testId++; tests.appendChild( li ); } }, setup: function() { if ( this.module !== config.previousModule ) { if ( config.previousModule ) { runLoggingCallbacks( "moduleDone", QUnit, { name: config.previousModule, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, total: config.moduleStats.all }); } config.previousModule = this.module; config.moduleStats = { all: 0, bad: 0 }; runLoggingCallbacks( "moduleStart", QUnit, { name: this.module }); } else if ( config.autorun ) { runLoggingCallbacks( "moduleStart", QUnit, { name: this.module }); } config.current = this; this.testEnvironment = extend({ setup: function() {}, teardown: function() {} }, this.moduleTestEnvironment ); runLoggingCallbacks( "testStart", QUnit, { name: this.testName, module: this.module }); // allow utility functions to access the current test environment // TODO why?? QUnit.current_testEnvironment = this.testEnvironment; if ( !config.pollution ) { saveGlobal(); } if ( config.notrycatch ) { this.testEnvironment.setup.call( this.testEnvironment ); return; } try { this.testEnvironment.setup.call( this.testEnvironment ); } catch( e ) { QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); } }, run: function() { config.current = this; var running = id( "qunit-testresult" ); if ( running ) { running.innerHTML = "Running:
" + this.name; } if ( this.async ) { QUnit.stop(); } if ( config.notrycatch ) { this.callback.call( this.testEnvironment, QUnit.assert ); return; } try { this.callback.call( this.testEnvironment, QUnit.assert ); } catch( e ) { QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) ); // else next test will carry the responsibility saveGlobal(); // Restart the tests if they're blocking if ( config.blocking ) { QUnit.start(); } } }, teardown: function() { config.current = this; if ( config.notrycatch ) { this.testEnvironment.teardown.call( this.testEnvironment ); return; } else { try { this.testEnvironment.teardown.call( this.testEnvironment ); } catch( e ) { QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); } } checkPollution(); }, finish: function() { config.current = this; if ( config.requireExpects && this.expected == null ) { QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); } else if ( this.expected != null && this.expected != this.assertions.length ) { QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); } else if ( this.expected == null && !this.assertions.length ) { QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); } var assertion, a, b, i, li, ol, test = this, good = 0, bad = 0, tests = id( "qunit-tests" ); config.stats.all += this.assertions.length; config.moduleStats.all += this.assertions.length; if ( tests ) { ol = document.createElement( "ol" ); for ( i = 0; i < this.assertions.length; i++ ) { assertion = this.assertions[i]; li = document.createElement( "li" ); li.className = assertion.result ? "pass" : "fail"; li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); ol.appendChild( li ); if ( assertion.result ) { good++; } else { bad++; config.stats.bad++; config.moduleStats.bad++; } } // store result when possible if ( QUnit.config.reorder && defined.sessionStorage ) { if ( bad ) { sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); } else { sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); } } if ( bad === 0 ) { ol.style.display = "none"; } // `b` initialized at top of scope b = document.createElement( "strong" ); b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; addEvent(b, "click", function() { var next = b.nextSibling.nextSibling, display = next.style.display; next.style.display = display === "none" ? "block" : "none"; }); addEvent(b, "dblclick", function( e ) { var target = e && e.target ? e.target : window.event.srcElement; if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { target = target.parentNode; } if ( window.location && target.nodeName.toLowerCase() === "strong" ) { window.location = QUnit.url({ testNumber: test.testNumber }); } }); // `li` initialized at top of scope li = id( this.id ); li.className = bad ? "fail" : "pass"; li.removeChild( li.firstChild ); a = li.firstChild; li.appendChild( b ); li.appendChild ( a ); li.appendChild( ol ); } else { for ( i = 0; i < this.assertions.length; i++ ) { if ( !this.assertions[i].result ) { bad++; config.stats.bad++; config.moduleStats.bad++; } } } runLoggingCallbacks( "testDone", QUnit, { name: this.testName, module: this.module, failed: bad, passed: this.assertions.length - bad, total: this.assertions.length }); QUnit.reset(); config.current = undefined; }, queue: function() { var bad, test = this; synchronize(function() { test.init(); }); function run() { // each of these can by async synchronize(function() { test.setup(); }); synchronize(function() { test.run(); }); synchronize(function() { test.teardown(); }); synchronize(function() { test.finish(); }); } // `bad` initialized at top of scope // defer when previous test run passed, if storage is available bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); if ( bad ) { run(); } else { synchronize( run, true ); } } }; // Root QUnit object. // `QUnit` initialized at top of scope QUnit = { // call on start of module test to prepend name to all tests module: function( name, testEnvironment ) { config.currentModule = name; config.currentModuleTestEnviroment = testEnvironment; }, asyncTest: function( testName, expected, callback ) { if ( arguments.length === 2 ) { callback = expected; expected = null; } QUnit.test( testName, expected, callback, true ); }, test: function( testName, expected, callback, async ) { var test, name = "" + escapeInnerText( testName ) + ""; if ( arguments.length === 2 ) { callback = expected; expected = null; } if ( config.currentModule ) { name = "" + config.currentModule + ": " + name; } test = new Test({ name: name, testName: testName, expected: expected, async: async, callback: callback, module: config.currentModule, moduleTestEnvironment: config.currentModuleTestEnviroment, stack: sourceFromStacktrace( 2 ) }); if ( !validTest( test ) ) { return; } test.queue(); }, // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. expect: function( asserts ) { config.current.expected = asserts; }, start: function( count ) { config.semaphore -= count || 1; // don't start until equal number of stop-calls if ( config.semaphore > 0 ) { return; } // ignore if start is called more often then stop if ( config.semaphore < 0 ) { config.semaphore = 0; } // A slight delay, to avoid any current callbacks if ( defined.setTimeout ) { window.setTimeout(function() { if ( config.semaphore > 0 ) { return; } if ( config.timeout ) { clearTimeout( config.timeout ); } config.blocking = false; process( true ); }, 13); } else { config.blocking = false; process( true ); } }, stop: function( count ) { config.semaphore += count || 1; config.blocking = true; if ( config.testTimeout && defined.setTimeout ) { clearTimeout( config.timeout ); config.timeout = window.setTimeout(function() { QUnit.ok( false, "Test timed out" ); config.semaphore = 1; QUnit.start(); }, config.testTimeout ); } } }; // Asssert helpers // All of these must call either QUnit.push() or manually do: // - runLoggingCallbacks( "log", .. ); // - config.current.assertions.push({ .. }); QUnit.assert = { /** * Asserts rough true-ish result. * @name ok * @function * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); */ ok: function( result, msg ) { if ( !config.current ) { throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); } result = !!result; var source, details = { result: result, message: msg }; msg = escapeInnerText( msg || (result ? "okay" : "failed" ) ); msg = "" + msg + ""; if ( !result ) { source = sourceFromStacktrace( 2 ); if ( source ) { details.source = source; msg += "
Source:
" + escapeInnerText( source ) + "
"; } } runLoggingCallbacks( "log", QUnit, details ); config.current.assertions.push({ result: result, message: msg }); }, /** * Assert that the first two arguments are equal, with an optional message. * Prints out both actual and expected values. * @name equal * @function * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); */ equal: function( actual, expected, message ) { QUnit.push( expected == actual, actual, expected, message ); }, /** * @name notEqual * @function */ notEqual: function( actual, expected, message ) { QUnit.push( expected != actual, actual, expected, message ); }, /** * @name deepEqual * @function */ deepEqual: function( actual, expected, message ) { QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); }, /** * @name notDeepEqual * @function */ notDeepEqual: function( actual, expected, message ) { QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); }, /** * @name strictEqual * @function */ strictEqual: function( actual, expected, message ) { QUnit.push( expected === actual, actual, expected, message ); }, /** * @name notStrictEqual * @function */ notStrictEqual: function( actual, expected, message ) { QUnit.push( expected !== actual, actual, expected, message ); }, throws: function( block, expected, message ) { var actual, ok = false; // 'expected' is optional if ( typeof expected === "string" ) { message = expected; expected = null; } config.current.ignoreGlobalErrors = true; try { block.call( config.current.testEnvironment ); } catch (e) { actual = e; } config.current.ignoreGlobalErrors = false; if ( actual ) { // we don't want to validate thrown error if ( !expected ) { ok = true; // expected is a regexp } else if ( QUnit.objectType( expected ) === "regexp" ) { ok = expected.test( actual ); // expected is a constructor } else if ( actual instanceof expected ) { ok = true; // expected is a validation function which returns true is validation passed } else if ( expected.call( {}, actual ) === true ) { ok = true; } QUnit.push( ok, actual, null, message ); } else { QUnit.pushFailure( message, null, 'No exception was thrown.' ); } } }; /** * @deprecate since 1.8.0 * Kept assertion helpers in root for backwards compatibility */ extend( QUnit, QUnit.assert ); /** * @deprecated since 1.9.0 * Kept global "raises()" for backwards compatibility */ QUnit.raises = QUnit.assert.throws; /** * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 * Kept to avoid TypeErrors for undefined methods. */ QUnit.equals = function() { QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); }; QUnit.same = function() { QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); }; // We want access to the constructor's prototype (function() { function F() {} F.prototype = QUnit; QUnit = new F(); // Make F QUnit's constructor so that we can add to the prototype later QUnit.constructor = F; }()); /** * Config object: Maintain internal state * Later exposed as QUnit.config * `config` initialized at top of scope */ config = { // The queue of tests to run queue: [], // block until document ready blocking: true, // when enabled, show only failing tests // gets persisted through sessionStorage and can be changed in UI via checkbox hidepassed: false, // by default, run previously failed tests first // very useful in combination with "Hide passed tests" checked reorder: true, // by default, modify document.title when suite is done altertitle: true, // when enabled, all tests must call expect() requireExpects: false, // add checkboxes that are persisted in the query-string // when enabled, the id is set to `true` as a `QUnit.config` property urlConfig: [ { id: "noglobals", label: "Check for Globals", tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." }, { id: "notrycatch", label: "No try-catch", tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." } ], // logging callback queues begin: [], done: [], log: [], testStart: [], testDone: [], moduleStart: [], moduleDone: [] }; // Initialize more QUnit.config and QUnit.urlParams (function() { var i, location = window.location || { search: "", protocol: "file:" }, params = location.search.slice( 1 ).split( "&" ), length = params.length, urlParams = {}, current; if ( params[ 0 ] ) { for ( i = 0; i < length; i++ ) { current = params[ i ].split( "=" ); current[ 0 ] = decodeURIComponent( current[ 0 ] ); // allow just a key to turn on a flag, e.g., test.html?noglobals current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; urlParams[ current[ 0 ] ] = current[ 1 ]; } } QUnit.urlParams = urlParams; // String search anywhere in moduleName+testName config.filter = urlParams.filter; // Exact match of the module name config.module = urlParams.module; config.testNumber = parseInt( urlParams.testNumber, 10 ) || null; // Figure out if we're running the tests from a server or not QUnit.isLocal = location.protocol === "file:"; }()); // Export global variables, unless an 'exports' object exists, // in that case we assume we're in CommonJS (dealt with on the bottom of the script) if ( typeof exports === "undefined" ) { extend( window, QUnit ); // Expose QUnit object window.QUnit = QUnit; } // Extend QUnit object, // these after set here because they should not be exposed as global functions extend( QUnit, { config: config, // Initialize the configuration options init: function() { extend( config, { stats: { all: 0, bad: 0 }, moduleStats: { all: 0, bad: 0 }, started: +new Date(), updateRate: 1000, blocking: false, autostart: true, autorun: false, filter: "", queue: [], semaphore: 0 }); var tests, banner, result, qunit = id( "qunit" ); if ( qunit ) { qunit.innerHTML = "

" + escapeInnerText( document.title ) + "

" + "

" + "
" + "

" + "
    "; } tests = id( "qunit-tests" ); banner = id( "qunit-banner" ); result = id( "qunit-testresult" ); if ( tests ) { tests.innerHTML = ""; } if ( banner ) { banner.className = ""; } if ( result ) { result.parentNode.removeChild( result ); } if ( tests ) { result = document.createElement( "p" ); result.id = "qunit-testresult"; result.className = "result"; tests.parentNode.insertBefore( result, tests ); result.innerHTML = "Running...
     "; } }, // Resets the test setup. Useful for tests that modify the DOM. // If jQuery is available, uses jQuery's html(), otherwise just innerHTML. reset: function() { var fixture; if ( window.jQuery ) { jQuery( "#qunit-fixture" ).html( config.fixture ); } else { fixture = id( "qunit-fixture" ); if ( fixture ) { fixture.innerHTML = config.fixture; } } }, // Trigger an event on an element. // @example triggerEvent( document.body, "click" ); triggerEvent: function( elem, type, event ) { if ( document.createEvent ) { event = document.createEvent( "MouseEvents" ); event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null); elem.dispatchEvent( event ); } else if ( elem.fireEvent ) { elem.fireEvent( "on" + type ); } }, // Safe object type checking is: function( type, obj ) { return QUnit.objectType( obj ) == type; }, objectType: function( obj ) { if ( typeof obj === "undefined" ) { return "undefined"; // consider: typeof null === object } if ( obj === null ) { return "null"; } var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ""; switch ( type ) { case "Number": if ( isNaN(obj) ) { return "nan"; } return "number"; case "String": case "Boolean": case "Array": case "Date": case "RegExp": case "Function": return type.toLowerCase(); } if ( typeof obj === "object" ) { return "object"; } return undefined; }, push: function( result, actual, expected, message ) { if ( !config.current ) { throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); } var output, source, details = { result: result, message: message, actual: actual, expected: expected }; message = escapeInnerText( message ) || ( result ? "okay" : "failed" ); message = "" + message + ""; output = message; if ( !result ) { expected = escapeInnerText( QUnit.jsDump.parse(expected) ); actual = escapeInnerText( QUnit.jsDump.parse(actual) ); output += ""; if ( actual != expected ) { output += ""; output += ""; } source = sourceFromStacktrace(); if ( source ) { details.source = source; output += ""; } output += "
    Expected:
    " + expected + "
    Result:
    " + actual + "
    Diff:
    " + QUnit.diff( expected, actual ) + "
    Source:
    " + escapeInnerText( source ) + "
    "; } runLoggingCallbacks( "log", QUnit, details ); config.current.assertions.push({ result: !!result, message: output }); }, pushFailure: function( message, source, actual ) { if ( !config.current ) { throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); } var output, details = { result: false, message: message }; message = escapeInnerText( message ) || "error"; message = "" + message + ""; output = message; output += ""; if ( actual ) { output += ""; } if ( source ) { details.source = source; output += ""; } output += "
    Result:
    " + escapeInnerText( actual ) + "
    Source:
    " + escapeInnerText( source ) + "
    "; runLoggingCallbacks( "log", QUnit, details ); config.current.assertions.push({ result: false, message: output }); }, url: function( params ) { params = extend( extend( {}, QUnit.urlParams ), params ); var key, querystring = "?"; for ( key in params ) { if ( !hasOwn.call( params, key ) ) { continue; } querystring += encodeURIComponent( key ) + "=" + encodeURIComponent( params[ key ] ) + "&"; } return window.location.pathname + querystring.slice( 0, -1 ); }, extend: extend, id: id, addEvent: addEvent // load, equiv, jsDump, diff: Attached later }); /** * @deprecated: Created for backwards compatibility with test runner that set the hook function * into QUnit.{hook}, instead of invoking it and passing the hook function. * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. * Doing this allows us to tell if the following methods have been overwritten on the actual * QUnit object. */ extend( QUnit.constructor.prototype, { // Logging callbacks; all receive a single argument with the listed properties // run test/logs.html for any related changes begin: registerLoggingCallback( "begin" ), // done: { failed, passed, total, runtime } done: registerLoggingCallback( "done" ), // log: { result, actual, expected, message } log: registerLoggingCallback( "log" ), // testStart: { name } testStart: registerLoggingCallback( "testStart" ), // testDone: { name, failed, passed, total } testDone: registerLoggingCallback( "testDone" ), // moduleStart: { name } moduleStart: registerLoggingCallback( "moduleStart" ), // moduleDone: { name, failed, passed, total } moduleDone: registerLoggingCallback( "moduleDone" ) }); if ( typeof document === "undefined" || document.readyState === "complete" ) { config.autorun = true; } QUnit.load = function() { runLoggingCallbacks( "begin", QUnit, {} ); // Initialize the config, saving the execution queue var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, urlConfigHtml = "", oldconfig = extend( {}, config ); QUnit.init(); extend(config, oldconfig); config.blocking = false; len = config.urlConfig.length; for ( i = 0; i < len; i++ ) { val = config.urlConfig[i]; if ( typeof val === "string" ) { val = { id: val, label: val, tooltip: "[no tooltip available]" }; } config[ val.id ] = QUnit.urlParams[ val.id ]; urlConfigHtml += ""; } // `userAgent` initialized at top of scope userAgent = id( "qunit-userAgent" ); if ( userAgent ) { userAgent.innerHTML = navigator.userAgent; } // `banner` initialized at top of scope banner = id( "qunit-header" ); if ( banner ) { banner.innerHTML = "" + banner.innerHTML + " "; } // `toolbar` initialized at top of scope toolbar = id( "qunit-testrunner-toolbar" ); if ( toolbar ) { // `filter` initialized at top of scope filter = document.createElement( "input" ); filter.type = "checkbox"; filter.id = "qunit-filter-pass"; addEvent( filter, "click", function() { var tmp, ol = document.getElementById( "qunit-tests" ); if ( filter.checked ) { ol.className = ol.className + " hidepass"; } else { tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; ol.className = tmp.replace( / hidepass /, " " ); } if ( defined.sessionStorage ) { if (filter.checked) { sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); } else { sessionStorage.removeItem( "qunit-filter-passed-tests" ); } } }); if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { filter.checked = true; // `ol` initialized at top of scope ol = document.getElementById( "qunit-tests" ); ol.className = ol.className + " hidepass"; } toolbar.appendChild( filter ); // `label` initialized at top of scope label = document.createElement( "label" ); label.setAttribute( "for", "qunit-filter-pass" ); label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." ); label.innerHTML = "Hide passed tests"; toolbar.appendChild( label ); urlConfigCheckboxes = document.createElement( 'span' ); urlConfigCheckboxes.innerHTML = urlConfigHtml; addEvent( urlConfigCheckboxes, "change", function( event ) { var params = {}; params[ event.target.name ] = event.target.checked ? true : undefined; window.location = QUnit.url( params ); }); toolbar.appendChild( urlConfigCheckboxes ); } // `main` initialized at top of scope main = id( "qunit-fixture" ); if ( main ) { config.fixture = main.innerHTML; } if ( config.autostart ) { QUnit.start(); } }; addEvent( window, "load", QUnit.load ); // `onErrorFnPrev` initialized at top of scope // Preserve other handlers onErrorFnPrev = window.onerror; // Cover uncaught exceptions // Returning true will surpress the default browser handler, // returning false will let it run. window.onerror = function ( error, filePath, linerNr ) { var ret = false; if ( onErrorFnPrev ) { ret = onErrorFnPrev( error, filePath, linerNr ); } // Treat return value as window.onerror itself does, // Only do our handling if not surpressed. if ( ret !== true ) { if ( QUnit.config.current ) { if ( QUnit.config.current.ignoreGlobalErrors ) { return true; } QUnit.pushFailure( error, filePath + ":" + linerNr ); } else { QUnit.test( "global failure", function() { QUnit.pushFailure( error, filePath + ":" + linerNr ); }); } return false; } return ret; }; function done() { config.autorun = true; // Log the last module results if ( config.currentModule ) { runLoggingCallbacks( "moduleDone", QUnit, { name: config.currentModule, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, total: config.moduleStats.all }); } var i, key, banner = id( "qunit-banner" ), tests = id( "qunit-tests" ), runtime = +new Date() - config.started, passed = config.stats.all - config.stats.bad, html = [ "Tests completed in ", runtime, " milliseconds.
    ", "", passed, " tests of ", config.stats.all, " passed, ", config.stats.bad, " failed." ].join( "" ); if ( banner ) { banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); } if ( tests ) { id( "qunit-testresult" ).innerHTML = html; } if ( config.altertitle && typeof document !== "undefined" && document.title ) { // show ✖ for good, ✔ for bad suite result in title // use escape sequences in case file gets loaded with non-utf-8-charset document.title = [ ( config.stats.bad ? "\u2716" : "\u2714" ), document.title.replace( /^[\u2714\u2716] /i, "" ) ].join( " " ); } // clear own sessionStorage items if all tests passed if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { // `key` & `i` initialized at top of scope for ( i = 0; i < sessionStorage.length; i++ ) { key = sessionStorage.key( i++ ); if ( key.indexOf( "qunit-test-" ) === 0 ) { sessionStorage.removeItem( key ); } } } runLoggingCallbacks( "done", QUnit, { failed: config.stats.bad, passed: passed, total: config.stats.all, runtime: runtime }); } /** @return Boolean: true if this test should be ran */ function validTest( test ) { var include, filter = config.filter && config.filter.toLowerCase(), module = config.module && config.module.toLowerCase(), fullName = (test.module + ": " + test.testName).toLowerCase(); if ( config.testNumber ) { return test.testNumber === config.testNumber; } if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { return false; } if ( !filter ) { return true; } include = filter.charAt( 0 ) !== "!"; if ( !include ) { filter = filter.slice( 1 ); } // If the filter matches, we need to honour include if ( fullName.indexOf( filter ) !== -1 ) { return include; } // Otherwise, do the opposite return !include; } // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) // Later Safari and IE10 are supposed to support error.stack as well // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack function extractStacktrace( e, offset ) { offset = offset === undefined ? 3 : offset; var stack, include, i, regex; if ( e.stacktrace ) { // Opera return e.stacktrace.split( "\n" )[ offset + 3 ]; } else if ( e.stack ) { // Firefox, Chrome stack = e.stack.split( "\n" ); if (/^error$/i.test( stack[0] ) ) { stack.shift(); } if ( fileName ) { include = []; for ( i = offset; i < stack.length; i++ ) { if ( stack[ i ].indexOf( fileName ) != -1 ) { break; } include.push( stack[ i ] ); } if ( include.length ) { return include.join( "\n" ); } } return stack[ offset ]; } else if ( e.sourceURL ) { // Safari, PhantomJS // hopefully one day Safari provides actual stacktraces // exclude useless self-reference for generated Error objects if ( /qunit.js$/.test( e.sourceURL ) ) { return; } // for actual exceptions, this is useful return e.sourceURL + ":" + e.line; } } function sourceFromStacktrace( offset ) { try { throw new Error(); } catch ( e ) { return extractStacktrace( e, offset ); } } function escapeInnerText( s ) { if ( !s ) { return ""; } s = s + ""; return s.replace( /[\&<>]/g, function( s ) { switch( s ) { case "&": return "&"; case "<": return "<"; case ">": return ">"; default: return s; } }); } function synchronize( callback, last ) { config.queue.push( callback ); if ( config.autorun && !config.blocking ) { process( last ); } } function process( last ) { function next() { process( last ); } var start = new Date().getTime(); config.depth = config.depth ? config.depth + 1 : 1; while ( config.queue.length && !config.blocking ) { if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { config.queue.shift()(); } else { window.setTimeout( next, 13 ); break; } } config.depth--; if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { done(); } } function saveGlobal() { config.pollution = []; if ( config.noglobals ) { for ( var key in window ) { // in Opera sometimes DOM element ids show up here, ignore them if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) { continue; } config.pollution.push( key ); } } } function checkPollution( name ) { var newGlobals, deletedGlobals, old = config.pollution; saveGlobal(); newGlobals = diff( config.pollution, old ); if ( newGlobals.length > 0 ) { QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); } deletedGlobals = diff( old, config.pollution ); if ( deletedGlobals.length > 0 ) { QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); } } // returns a new Array with the elements that are in a but not in b function diff( a, b ) { var i, j, result = a.slice(); for ( i = 0; i < result.length; i++ ) { for ( j = 0; j < b.length; j++ ) { if ( result[i] === b[j] ) { result.splice( i, 1 ); i--; break; } } } return result; } function extend( a, b ) { for ( var prop in b ) { if ( b[ prop ] === undefined ) { delete a[ prop ]; // Avoid "Member not found" error in IE8 caused by setting window.constructor } else if ( prop !== "constructor" || a !== window ) { a[ prop ] = b[ prop ]; } } return a; } function addEvent( elem, type, fn ) { if ( elem.addEventListener ) { elem.addEventListener( type, fn, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, fn ); } else { fn(); } } function id( name ) { return !!( typeof document !== "undefined" && document && document.getElementById ) && document.getElementById( name ); } function registerLoggingCallback( key ) { return function( callback ) { config[key].push( callback ); }; } // Supports deprecated method of completely overwriting logging callbacks function runLoggingCallbacks( key, scope, args ) { //debugger; var i, callbacks; if ( QUnit.hasOwnProperty( key ) ) { QUnit[ key ].call(scope, args ); } else { callbacks = config[ key ]; for ( i = 0; i < callbacks.length; i++ ) { callbacks[ i ].call( scope, args ); } } } // Test for equality any JavaScript type. // Author: Philippe Rathé QUnit.equiv = (function() { // Call the o related callback with the given arguments. function bindCallbacks( o, callbacks, args ) { var prop = QUnit.objectType( o ); if ( prop ) { if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { return callbacks[ prop ].apply( callbacks, args ); } else { return callbacks[ prop ]; // or undefined } } } // the real equiv function var innerEquiv, // stack to decide between skip/abort functions callers = [], // stack to avoiding loops from circular referencing parents = [], getProto = Object.getPrototypeOf || function ( obj ) { return obj.__proto__; }, callbacks = (function () { // for string, boolean, number and null function useStrictEquality( b, a ) { if ( b instanceof a.constructor || a instanceof b.constructor ) { // to catch short annotaion VS 'new' annotation of a // declaration // e.g. var i = 1; // var j = new Number(1); return a == b; } else { return a === b; } } return { "string": useStrictEquality, "boolean": useStrictEquality, "number": useStrictEquality, "null": useStrictEquality, "undefined": useStrictEquality, "nan": function( b ) { return isNaN( b ); }, "date": function( b, a ) { return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); }, "regexp": function( b, a ) { return QUnit.objectType( b ) === "regexp" && // the regex itself a.source === b.source && // and its modifers a.global === b.global && // (gmi) ... a.ignoreCase === b.ignoreCase && a.multiline === b.multiline; }, // - skip when the property is a method of an instance (OOP) // - abort otherwise, // initial === would have catch identical references anyway "function": function() { var caller = callers[callers.length - 1]; return caller !== Object && typeof caller !== "undefined"; }, "array": function( b, a ) { var i, j, len, loop; // b could be an object literal here if ( QUnit.objectType( b ) !== "array" ) { return false; } len = a.length; if ( len !== b.length ) { // safe and faster return false; } // track reference to avoid circular references parents.push( a ); for ( i = 0; i < len; i++ ) { loop = false; for ( j = 0; j < parents.length; j++ ) { if ( parents[j] === a[i] ) { loop = true;// dont rewalk array } } if ( !loop && !innerEquiv(a[i], b[i]) ) { parents.pop(); return false; } } parents.pop(); return true; }, "object": function( b, a ) { var i, j, loop, // Default to true eq = true, aProperties = [], bProperties = []; // comparing constructors is more strict than using // instanceof if ( a.constructor !== b.constructor ) { // Allow objects with no prototype to be equivalent to // objects with Object as their constructor. if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { return false; } } // stack constructor before traversing properties callers.push( a.constructor ); // track reference to avoid circular references parents.push( a ); for ( i in a ) { // be strict: don't ensures hasOwnProperty // and go deep loop = false; for ( j = 0; j < parents.length; j++ ) { if ( parents[j] === a[i] ) { // don't go down the same path twice loop = true; } } aProperties.push(i); // collect a's properties if (!loop && !innerEquiv( a[i], b[i] ) ) { eq = false; break; } } callers.pop(); // unstack, we are done parents.pop(); for ( i in b ) { bProperties.push( i ); // collect b's properties } // Ensures identical properties name return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); } }; }()); innerEquiv = function() { // can take multiple arguments var args = [].slice.apply( arguments ); if ( args.length < 2 ) { return true; // end transition } return (function( a, b ) { if ( a === b ) { return true; // catch the most you can } else if ( a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b) ) { return false; // don't lose time with error prone cases } else { return bindCallbacks(a, callbacks, [ b, a ]); } // apply transition with (1..n) arguments }( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) ); }; return innerEquiv; }()); /** * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | * http://flesler.blogspot.com Licensed under BSD * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 * * @projectDescription Advanced and extensible data dumping for Javascript. * @version 1.0.0 * @author Ariel Flesler * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} */ QUnit.jsDump = (function() { function quote( str ) { return '"' + str.toString().replace( /"/g, '\\"' ) + '"'; } function literal( o ) { return o + ""; } function join( pre, arr, post ) { var s = jsDump.separator(), base = jsDump.indent(), inner = jsDump.indent(1); if ( arr.join ) { arr = arr.join( "," + s + inner ); } if ( !arr ) { return pre + post; } return [ pre, inner + arr, base + post ].join(s); } function array( arr, stack ) { var i = arr.length, ret = new Array(i); this.up(); while ( i-- ) { ret[i] = this.parse( arr[i] , undefined , stack); } this.down(); return join( "[", ret, "]" ); } var reName = /^function (\w+)/, jsDump = { parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance stack = stack || [ ]; var inStack, res, parser = this.parsers[ type || this.typeOf(obj) ]; type = typeof parser; inStack = inArray( obj, stack ); if ( inStack != -1 ) { return "recursion(" + (inStack - stack.length) + ")"; } //else if ( type == "function" ) { stack.push( obj ); res = parser.call( this, obj, stack ); stack.pop(); return res; } // else return ( type == "string" ) ? parser : this.parsers.error; }, typeOf: function( obj ) { var type; if ( obj === null ) { type = "null"; } else if ( typeof obj === "undefined" ) { type = "undefined"; } else if ( QUnit.is( "regexp", obj) ) { type = "regexp"; } else if ( QUnit.is( "date", obj) ) { type = "date"; } else if ( QUnit.is( "function", obj) ) { type = "function"; } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { type = "window"; } else if ( obj.nodeType === 9 ) { type = "document"; } else if ( obj.nodeType ) { type = "node"; } else if ( // native arrays toString.call( obj ) === "[object Array]" || // NodeList objects ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) ) { type = "array"; } else { type = typeof obj; } return type; }, separator: function() { return this.multiline ? this.HTML ? "
    " : "\n" : this.HTML ? " " : " "; }, indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing if ( !this.multiline ) { return ""; } var chr = this.indentChar; if ( this.HTML ) { chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); } return new Array( this._depth_ + (extra||0) ).join(chr); }, up: function( a ) { this._depth_ += a || 1; }, down: function( a ) { this._depth_ -= a || 1; }, setParser: function( name, parser ) { this.parsers[name] = parser; }, // The next 3 are exposed so you can use them quote: quote, literal: literal, join: join, // _depth_: 1, // This is the list of parsers, to modify them, use jsDump.setParser parsers: { window: "[Window]", document: "[Document]", error: "[ERROR]", //when no parser is found, shouldn"t happen unknown: "[Unknown]", "null": "null", "undefined": "undefined", "function": function( fn ) { var ret = "function", name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE if ( name ) { ret += " " + name; } ret += "( "; ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); }, array: array, nodelist: array, "arguments": array, object: function( map, stack ) { var ret = [ ], keys, key, val, i; QUnit.jsDump.up(); if ( Object.keys ) { keys = Object.keys( map ); } else { keys = []; for ( key in map ) { keys.push( key ); } } keys.sort(); for ( i = 0; i < keys.length; i++ ) { key = keys[ i ]; val = map[ key ]; ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); } QUnit.jsDump.down(); return join( "{", ret, "}" ); }, node: function( node ) { var a, val, open = QUnit.jsDump.HTML ? "<" : "<", close = QUnit.jsDump.HTML ? ">" : ">", tag = node.nodeName.toLowerCase(), ret = open + tag; for ( a in QUnit.jsDump.DOMAttrs ) { val = node[ QUnit.jsDump.DOMAttrs[a] ]; if ( val ) { ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" ); } } return ret + close + open + "/" + tag + close; }, functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function var args, l = fn.length; if ( !l ) { return ""; } args = new Array(l); while ( l-- ) { args[l] = String.fromCharCode(97+l);//97 is 'a' } return " " + args.join( ", " ) + " "; }, key: quote, //object calls it internally, the key part of an item in a map functionCode: "[code]", //function calls it internally, it's the content of the function attribute: quote, //node calls it internally, it's an html attribute value string: quote, date: quote, regexp: literal, //regex number: literal, "boolean": literal }, DOMAttrs: { //attributes to dump from nodes, name=>realName id: "id", name: "name", "class": "className" }, HTML: false,//if true, entities are escaped ( <, >, \t, space and \n ) indentChar: " ",//indentation unit multiline: true //if true, items in a collection, are separated by a \n, else just a space. }; return jsDump; }()); // from Sizzle.js function getText( elems ) { var i, elem, ret = ""; for ( i = 0; elems[i]; i++ ) { elem = elems[i]; // Get the text from text nodes and CDATA nodes if ( elem.nodeType === 3 || elem.nodeType === 4 ) { ret += elem.nodeValue; // Traverse everything else, except comment nodes } else if ( elem.nodeType !== 8 ) { ret += getText( elem.childNodes ); } } return ret; } // from jquery.js function inArray( elem, array ) { if ( array.indexOf ) { return array.indexOf( elem ); } for ( var i = 0, length = array.length; i < length; i++ ) { if ( array[ i ] === elem ) { return i; } } return -1; } /* * Javascript Diff Algorithm * By John Resig (http://ejohn.org/) * Modified by Chu Alan "sprite" * * Released under the MIT license. * * More Info: * http://ejohn.org/projects/javascript-diff-algorithm/ * * Usage: QUnit.diff(expected, actual) * * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" */ QUnit.diff = (function() { function diff( o, n ) { var i, ns = {}, os = {}; for ( i = 0; i < n.length; i++ ) { if ( ns[ n[i] ] == null ) { ns[ n[i] ] = { rows: [], o: null }; } ns[ n[i] ].rows.push( i ); } for ( i = 0; i < o.length; i++ ) { if ( os[ o[i] ] == null ) { os[ o[i] ] = { rows: [], n: null }; } os[ o[i] ].rows.push( i ); } for ( i in ns ) { if ( !hasOwn.call( ns, i ) ) { continue; } if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) { n[ ns[i].rows[0] ] = { text: n[ ns[i].rows[0] ], row: os[i].rows[0] }; o[ os[i].rows[0] ] = { text: o[ os[i].rows[0] ], row: ns[i].rows[0] }; } } for ( i = 0; i < n.length - 1; i++ ) { if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && n[ i + 1 ] == o[ n[i].row + 1 ] ) { n[ i + 1 ] = { text: n[ i + 1 ], row: n[i].row + 1 }; o[ n[i].row + 1 ] = { text: o[ n[i].row + 1 ], row: i + 1 }; } } for ( i = n.length - 1; i > 0; i-- ) { if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && n[ i - 1 ] == o[ n[i].row - 1 ]) { n[ i - 1 ] = { text: n[ i - 1 ], row: n[i].row - 1 }; o[ n[i].row - 1 ] = { text: o[ n[i].row - 1 ], row: i - 1 }; } } return { o: o, n: n }; } return function( o, n ) { o = o.replace( /\s+$/, "" ); n = n.replace( /\s+$/, "" ); var i, pre, str = "", out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), oSpace = o.match(/\s+/g), nSpace = n.match(/\s+/g); if ( oSpace == null ) { oSpace = [ " " ]; } else { oSpace.push( " " ); } if ( nSpace == null ) { nSpace = [ " " ]; } else { nSpace.push( " " ); } if ( out.n.length === 0 ) { for ( i = 0; i < out.o.length; i++ ) { str += "" + out.o[i] + oSpace[i] + ""; } } else { if ( out.n[0].text == null ) { for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { str += "" + out.o[n] + oSpace[n] + ""; } } for ( i = 0; i < out.n.length; i++ ) { if (out.n[i].text == null) { str += "" + out.n[i] + nSpace[i] + ""; } else { // `pre` initialized at top of scope pre = ""; for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { pre += "" + out.o[n] + oSpace[n] + ""; } str += " " + out.n[i].text + nSpace[i] + pre; } } } return str; }; }()); // for CommonJS enviroments, export everything if ( typeof exports !== "undefined" ) { extend(exports, QUnit); } // get at whatever the global object is, like window in browsers }( (function() {return this;}.call()) )); gevent-socketio-0.3.6/tests/jstests/static/socket.io.js0000644000175000017500000030570012261120545024045 0ustar sonteksontek00000000000000/*! Socket.IO.js build:0.9.16, development. Copyright(c) 2011 LearnBoost MIT Licensed */ var io = ('undefined' === typeof module ? {} : module.exports); (function() { /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, global) { /** * IO namespace. * * @namespace */ var io = exports; /** * Socket.IO version * * @api public */ io.version = '0.9.16'; /** * Protocol implemented. * * @api public */ io.protocol = 1; /** * Available transports, these will be populated with the available transports * * @api public */ io.transports = []; /** * Keep track of jsonp callbacks. * * @api private */ io.j = []; /** * Keep track of our io.Sockets * * @api private */ io.sockets = {}; /** * Manages connections to hosts. * * @param {String} uri * @Param {Boolean} force creation of new socket (defaults to false) * @api public */ io.connect = function (host, details) { var uri = io.util.parseUri(host) , uuri , socket; if (global && global.location) { uri.protocol = uri.protocol || global.location.protocol.slice(0, -1); uri.host = uri.host || (global.document ? global.document.domain : global.location.hostname); uri.port = uri.port || global.location.port; } uuri = io.util.uniqueUri(uri); var options = { host: uri.host , secure: 'https' == uri.protocol , port: uri.port || ('https' == uri.protocol ? 443 : 80) , query: uri.query || '' }; io.util.merge(options, details); if (options['force new connection'] || !io.sockets[uuri]) { socket = new io.Socket(options); } if (!options['force new connection'] && socket) { io.sockets[uuri] = socket; } socket = socket || io.sockets[uuri]; // if path is different from '' or / return socket.of(uri.path.length > 1 ? uri.path : ''); }; })('object' === typeof module ? module.exports : (this.io = {}), this); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, global) { /** * Utilities namespace. * * @namespace */ var util = exports.util = {}; /** * Parses an URI * * @author Steven Levithan (MIT license) * @api public */ var re = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; var parts = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor']; util.parseUri = function (str) { var m = re.exec(str || '') , uri = {} , i = 14; while (i--) { uri[parts[i]] = m[i] || ''; } return uri; }; /** * Produces a unique url that identifies a Socket.IO connection. * * @param {Object} uri * @api public */ util.uniqueUri = function (uri) { var protocol = uri.protocol , host = uri.host , port = uri.port; if ('document' in global) { host = host || document.domain; port = port || (protocol == 'https' && document.location.protocol !== 'https:' ? 443 : document.location.port); } else { host = host || 'localhost'; if (!port && protocol == 'https') { port = 443; } } return (protocol || 'http') + '://' + host + ':' + (port || 80); }; /** * Mergest 2 query strings in to once unique query string * * @param {String} base * @param {String} addition * @api public */ util.query = function (base, addition) { var query = util.chunkQuery(base || '') , components = []; util.merge(query, util.chunkQuery(addition || '')); for (var part in query) { if (query.hasOwnProperty(part)) { components.push(part + '=' + query[part]); } } return components.length ? '?' + components.join('&') : ''; }; /** * Transforms a querystring in to an object * * @param {String} qs * @api public */ util.chunkQuery = function (qs) { var query = {} , params = qs.split('&') , i = 0 , l = params.length , kv; for (; i < l; ++i) { kv = params[i].split('='); if (kv[0]) { query[kv[0]] = kv[1]; } } return query; }; /** * Executes the given function when the page is loaded. * * io.util.load(function () { console.log('page loaded'); }); * * @param {Function} fn * @api public */ var pageLoaded = false; util.load = function (fn) { if ('document' in global && document.readyState === 'complete' || pageLoaded) { return fn(); } util.on(global, 'load', fn, false); }; /** * Adds an event. * * @api private */ util.on = function (element, event, fn, capture) { if (element.attachEvent) { element.attachEvent('on' + event, fn); } else if (element.addEventListener) { element.addEventListener(event, fn, capture); } }; /** * Generates the correct `XMLHttpRequest` for regular and cross domain requests. * * @param {Boolean} [xdomain] Create a request that can be used cross domain. * @returns {XMLHttpRequest|false} If we can create a XMLHttpRequest. * @api private */ util.request = function (xdomain) { if (xdomain && 'undefined' != typeof XDomainRequest && !util.ua.hasCORS) { return new XDomainRequest(); } if ('undefined' != typeof XMLHttpRequest && (!xdomain || util.ua.hasCORS)) { return new XMLHttpRequest(); } if (!xdomain) { try { return new window[(['Active'].concat('Object').join('X'))]('Microsoft.XMLHTTP'); } catch(e) { } } return null; }; /** * XHR based transport constructor. * * @constructor * @api public */ /** * Change the internal pageLoaded value. */ if ('undefined' != typeof window) { util.load(function () { pageLoaded = true; }); } /** * Defers a function to ensure a spinner is not displayed by the browser * * @param {Function} fn * @api public */ util.defer = function (fn) { if (!util.ua.webkit || 'undefined' != typeof importScripts) { return fn(); } util.load(function () { setTimeout(fn, 100); }); }; /** * Merges two objects. * * @api public */ util.merge = function merge (target, additional, deep, lastseen) { var seen = lastseen || [] , depth = typeof deep == 'undefined' ? 2 : deep , prop; for (prop in additional) { if (additional.hasOwnProperty(prop) && util.indexOf(seen, prop) < 0) { if (typeof target[prop] !== 'object' || !depth) { target[prop] = additional[prop]; seen.push(additional[prop]); } else { util.merge(target[prop], additional[prop], depth - 1, seen); } } } return target; }; /** * Merges prototypes from objects * * @api public */ util.mixin = function (ctor, ctor2) { util.merge(ctor.prototype, ctor2.prototype); }; /** * Shortcut for prototypical and static inheritance. * * @api private */ util.inherit = function (ctor, ctor2) { function f() {}; f.prototype = ctor2.prototype; ctor.prototype = new f; }; /** * Checks if the given object is an Array. * * io.util.isArray([]); // true * io.util.isArray({}); // false * * @param Object obj * @api public */ util.isArray = Array.isArray || function (obj) { return Object.prototype.toString.call(obj) === '[object Array]'; }; /** * Intersects values of two arrays into a third * * @api public */ util.intersect = function (arr, arr2) { var ret = [] , longest = arr.length > arr2.length ? arr : arr2 , shortest = arr.length > arr2.length ? arr2 : arr; for (var i = 0, l = shortest.length; i < l; i++) { if (~util.indexOf(longest, shortest[i])) ret.push(shortest[i]); } return ret; }; /** * Array indexOf compatibility. * * @see bit.ly/a5Dxa2 * @api public */ util.indexOf = function (arr, o, i) { for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0; i < j && arr[i] !== o; i++) {} return j <= i ? -1 : i; }; /** * Converts enumerables to array. * * @api public */ util.toArray = function (enu) { var arr = []; for (var i = 0, l = enu.length; i < l; i++) arr.push(enu[i]); return arr; }; /** * UA / engines detection namespace. * * @namespace */ util.ua = {}; /** * Whether the UA supports CORS for XHR. * * @api public */ util.ua.hasCORS = 'undefined' != typeof XMLHttpRequest && (function () { try { var a = new XMLHttpRequest(); } catch (e) { return false; } return a.withCredentials != undefined; })(); /** * Detect webkit. * * @api public */ util.ua.webkit = 'undefined' != typeof navigator && /webkit/i.test(navigator.userAgent); /** * Detect iPad/iPhone/iPod. * * @api public */ util.ua.iDevice = 'undefined' != typeof navigator && /iPad|iPhone|iPod/i.test(navigator.userAgent); })('undefined' != typeof io ? io : module.exports, this); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io) { /** * Expose constructor. */ exports.EventEmitter = EventEmitter; /** * Event emitter constructor. * * @api public. */ function EventEmitter () {}; /** * Adds a listener * * @api public */ EventEmitter.prototype.on = function (name, fn) { if (!this.$events) { this.$events = {}; } if (!this.$events[name]) { this.$events[name] = fn; } else if (io.util.isArray(this.$events[name])) { this.$events[name].push(fn); } else { this.$events[name] = [this.$events[name], fn]; } return this; }; EventEmitter.prototype.addListener = EventEmitter.prototype.on; /** * Adds a volatile listener. * * @api public */ EventEmitter.prototype.once = function (name, fn) { var self = this; function on () { self.removeListener(name, on); fn.apply(this, arguments); }; on.listener = fn; this.on(name, on); return this; }; /** * Removes a listener. * * @api public */ EventEmitter.prototype.removeListener = function (name, fn) { if (this.$events && this.$events[name]) { var list = this.$events[name]; if (io.util.isArray(list)) { var pos = -1; for (var i = 0, l = list.length; i < l; i++) { if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { pos = i; break; } } if (pos < 0) { return this; } list.splice(pos, 1); if (!list.length) { delete this.$events[name]; } } else if (list === fn || (list.listener && list.listener === fn)) { delete this.$events[name]; } } return this; }; /** * Removes all listeners for an event. * * @api public */ EventEmitter.prototype.removeAllListeners = function (name) { if (name === undefined) { this.$events = {}; return this; } if (this.$events && this.$events[name]) { this.$events[name] = null; } return this; }; /** * Gets all listeners for a certain event. * * @api publci */ EventEmitter.prototype.listeners = function (name) { if (!this.$events) { this.$events = {}; } if (!this.$events[name]) { this.$events[name] = []; } if (!io.util.isArray(this.$events[name])) { this.$events[name] = [this.$events[name]]; } return this.$events[name]; }; /** * Emits an event. * * @api public */ EventEmitter.prototype.emit = function (name) { if (!this.$events) { return false; } var handler = this.$events[name]; if (!handler) { return false; } var args = Array.prototype.slice.call(arguments, 1); if ('function' == typeof handler) { handler.apply(this, args); } else if (io.util.isArray(handler)) { var listeners = handler.slice(); for (var i = 0, l = listeners.length; i < l; i++) { listeners[i].apply(this, args); } } else { return false; } return true; }; })( 'undefined' != typeof io ? io : module.exports , 'undefined' != typeof io ? io : module.parent.exports ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ /** * Based on JSON2 (http://www.JSON.org/js.html). */ (function (exports, nativeJSON) { "use strict"; // use native JSON if it's available if (nativeJSON && nativeJSON.parse){ return exports.JSON = { parse: nativeJSON.parse , stringify: nativeJSON.stringify }; } var JSON = exports.JSON = {}; function f(n) { // Format integers to have at least two digits. return n < 10 ? '0' + n : n; } function date(d, key) { return isFinite(d.valueOf()) ? d.getUTCFullYear() + '-' + f(d.getUTCMonth() + 1) + '-' + f(d.getUTCDate()) + 'T' + f(d.getUTCHours()) + ':' + f(d.getUTCMinutes()) + ':' + f(d.getUTCSeconds()) + 'Z' : null; }; var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, gap, indent, meta = { // table of character substitutions '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\' }, rep; function quote(string) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. // Otherwise we must also replace the offending characters with safe escape // sequences. escapable.lastIndex = 0; return escapable.test(string) ? '"' + string.replace(escapable, function (a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; } function str(key, holder) { // Produce a string from holder[key]. var i, // The loop counter. k, // The member key. v, // The member value. length, mind = gap, partial, value = holder[key]; // If the value has a toJSON method, call it to obtain a replacement value. if (value instanceof Date) { value = date(key); } // If we were called with a replacer function, then call the replacer to // obtain a replacement value. if (typeof rep === 'function') { value = rep.call(holder, key, value); } // What happens next depends on the value's type. switch (typeof value) { case 'string': return quote(value); case 'number': // JSON numbers must be finite. Encode non-finite numbers as null. return isFinite(value) ? String(value) : 'null'; case 'boolean': case 'null': // If the value is a boolean or null, convert it to a string. Note: // typeof null does not produce 'null'. The case is included here in // the remote chance that this gets fixed someday. return String(value); // If the type is 'object', we might be dealing with an object or an array or // null. case 'object': // Due to a specification blunder in ECMAScript, typeof null is 'object', // so watch out for that case. if (!value) { return 'null'; } // Make an array to hold the partial results of stringifying this object value. gap += indent; partial = []; // Is the value an array? if (Object.prototype.toString.apply(value) === '[object Array]') { // The value is an array. Stringify every element. Use null as a placeholder // for non-JSON values. length = value.length; for (i = 0; i < length; i += 1) { partial[i] = str(i, value) || 'null'; } // Join all of the elements together, separated with commas, and wrap them in // brackets. v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']'; gap = mind; return v; } // If the replacer is an array, use it to select the members to be stringified. if (rep && typeof rep === 'object') { length = rep.length; for (i = 0; i < length; i += 1) { if (typeof rep[i] === 'string') { k = rep[i]; v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } else { // Otherwise, iterate through all of the keys in the object. for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } // Join all of the member texts together, separated with commas, // and wrap them in braces. v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}'; gap = mind; return v; } } // If the JSON object does not yet have a stringify method, give it one. JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional // space parameter, and returns a JSON text. The replacer can be a function // that can replace values, or an array of strings that will select the keys. // A default replacer method can be provided. Use of the space parameter can // produce text that is more easily readable. var i; gap = ''; indent = ''; // If the space parameter is a number, make an indent string containing that // many spaces. if (typeof space === 'number') { for (i = 0; i < space; i += 1) { indent += ' '; } // If the space parameter is a string, it will be used as the indent string. } else if (typeof space === 'string') { indent = space; } // If there is a replacer, it must be a function or an array. // Otherwise, throw an error. rep = replacer; if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number')) { throw new Error('JSON.stringify'); } // Make a fake root object containing our value under the key of ''. // Return the result of stringifying the value. return str('', {'': value}); }; // If the JSON object does not yet have a parse method, give it one. JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns // a JavaScript value if the text is a valid JSON text. var j; function walk(holder, key) { // The walk method is used to recursively walk the resulting structure so // that modifications can be made. var k, v, value = holder[key]; if (value && typeof value === 'object') { for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = walk(value, k); if (v !== undefined) { value[k] = v; } else { delete value[k]; } } } } return reviver.call(holder, key, value); } // Parsing happens in four stages. In the first stage, we replace certain // Unicode characters with escape sequences. JavaScript handles many characters // incorrectly, either silently deleting them, or treating them as line endings. text = String(text); cx.lastIndex = 0; if (cx.test(text)) { text = text.replace(cx, function (a) { return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }); } // In the second stage, we run the text against regular expressions that look // for non-JSON patterns. We are especially concerned with '()' and 'new' // because they can cause invocation, and '=' because it can cause mutation. // But just to be safe, we want to reject all unexpected forms. // We split the second stage into 4 regexp operations in order to work around // crippling inefficiencies in IE's and Safari's regexp engines. First we // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we // replace all simple value tokens with ']' characters. Third, we delete all // open brackets that follow a colon or comma or that begin the text. Finally, // we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. if (/^[\],:{}\s]*$/ .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { // In the third stage we use the eval function to compile the text into a // JavaScript structure. The '{' operator is subject to a syntactic ambiguity // in JavaScript: it can begin a block or an object literal. We wrap the text // in parens to eliminate the ambiguity. j = eval('(' + text + ')'); // In the optional fourth stage, we recursively walk the new structure, passing // each name/value pair to a reviver function for possible transformation. return typeof reviver === 'function' ? walk({'': j}, '') : j; } // If the text is not JSON parseable, then a SyntaxError is thrown. throw new SyntaxError('JSON.parse'); }; })( 'undefined' != typeof io ? io : module.exports , typeof JSON !== 'undefined' ? JSON : undefined ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io) { /** * Parser namespace. * * @namespace */ var parser = exports.parser = {}; /** * Packet types. */ var packets = parser.packets = [ 'disconnect' , 'connect' , 'heartbeat' , 'message' , 'json' , 'event' , 'ack' , 'error' , 'noop' ]; /** * Errors reasons. */ var reasons = parser.reasons = [ 'transport not supported' , 'client not handshaken' , 'unauthorized' ]; /** * Errors advice. */ var advice = parser.advice = [ 'reconnect' ]; /** * Shortcuts. */ var JSON = io.JSON , indexOf = io.util.indexOf; /** * Encodes a packet. * * @api private */ parser.encodePacket = function (packet) { var type = indexOf(packets, packet.type) , id = packet.id || '' , endpoint = packet.endpoint || '' , ack = packet.ack , data = null; switch (packet.type) { case 'error': var reason = packet.reason ? indexOf(reasons, packet.reason) : '' , adv = packet.advice ? indexOf(advice, packet.advice) : ''; if (reason !== '' || adv !== '') data = reason + (adv !== '' ? ('+' + adv) : ''); break; case 'message': if (packet.data !== '') data = packet.data; break; case 'event': var ev = { name: packet.name }; if (packet.args && packet.args.length) { ev.args = packet.args; } data = JSON.stringify(ev); break; case 'json': data = JSON.stringify(packet.data); break; case 'connect': if (packet.qs) data = packet.qs; break; case 'ack': data = packet.ackId + (packet.args && packet.args.length ? '+' + JSON.stringify(packet.args) : ''); break; } // construct packet with required fragments var encoded = [ type , id + (ack == 'data' ? '+' : '') , endpoint ]; // data fragment is optional if (data !== null && data !== undefined) encoded.push(data); return encoded.join(':'); }; /** * Encodes multiple messages (payload). * * @param {Array} messages * @api private */ parser.encodePayload = function (packets) { var decoded = ''; if (packets.length == 1) return packets[0]; for (var i = 0, l = packets.length; i < l; i++) { var packet = packets[i]; decoded += '\ufffd' + packet.length + '\ufffd' + packets[i]; } return decoded; }; /** * Decodes a packet * * @api private */ var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/; parser.decodePacket = function (data) { var pieces = data.match(regexp); if (!pieces) return {}; var id = pieces[2] || '' , data = pieces[5] || '' , packet = { type: packets[pieces[1]] , endpoint: pieces[4] || '' }; // whether we need to acknowledge the packet if (id) { packet.id = id; if (pieces[3]) packet.ack = 'data'; else packet.ack = true; } // handle different packet types switch (packet.type) { case 'error': var pieces = data.split('+'); packet.reason = reasons[pieces[0]] || ''; packet.advice = advice[pieces[1]] || ''; break; case 'message': packet.data = data || ''; break; case 'event': try { var opts = JSON.parse(data); packet.name = opts.name; packet.args = opts.args; } catch (e) { } packet.args = packet.args || []; break; case 'json': try { packet.data = JSON.parse(data); } catch (e) { } break; case 'connect': packet.qs = data || ''; break; case 'ack': var pieces = data.match(/^([0-9]+)(\+)?(.*)/); if (pieces) { packet.ackId = pieces[1]; packet.args = []; if (pieces[3]) { try { packet.args = pieces[3] ? JSON.parse(pieces[3]) : []; } catch (e) { } } } break; case 'disconnect': case 'heartbeat': break; }; return packet; }; /** * Decodes data payload. Detects multiple messages * * @return {Array} messages * @api public */ parser.decodePayload = function (data) { // IE doesn't like data[i] for unicode chars, charAt works fine if (data.charAt(0) == '\ufffd') { var ret = []; for (var i = 1, length = ''; i < data.length; i++) { if (data.charAt(i) == '\ufffd') { ret.push(parser.decodePacket(data.substr(i + 1).substr(0, length))); i += Number(length) + 1; length = ''; } else { length += data.charAt(i); } } return ret; } else { return [parser.decodePacket(data)]; } }; })( 'undefined' != typeof io ? io : module.exports , 'undefined' != typeof io ? io : module.parent.exports ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io) { /** * Expose constructor. */ exports.Transport = Transport; /** * This is the transport template for all supported transport methods. * * @constructor * @api public */ function Transport (socket, sessid) { this.socket = socket; this.sessid = sessid; }; /** * Apply EventEmitter mixin. */ io.util.mixin(Transport, io.EventEmitter); /** * Indicates whether heartbeats is enabled for this transport * * @api private */ Transport.prototype.heartbeats = function () { return true; }; /** * Handles the response from the server. When a new response is received * it will automatically update the timeout, decode the message and * forwards the response to the onMessage function for further processing. * * @param {String} data Response from the server. * @api private */ Transport.prototype.onData = function (data) { this.clearCloseTimeout(); // If the connection in currently open (or in a reopening state) reset the close // timeout since we have just received data. This check is necessary so // that we don't reset the timeout on an explicitly disconnected connection. if (this.socket.connected || this.socket.connecting || this.socket.reconnecting) { this.setCloseTimeout(); } if (data !== '') { // todo: we should only do decodePayload for xhr transports var msgs = io.parser.decodePayload(data); if (msgs && msgs.length) { for (var i = 0, l = msgs.length; i < l; i++) { this.onPacket(msgs[i]); } } } return this; }; /** * Handles packets. * * @api private */ Transport.prototype.onPacket = function (packet) { this.socket.setHeartbeatTimeout(); if (packet.type == 'heartbeat') { return this.onHeartbeat(); } if (packet.type == 'connect' && packet.endpoint == '') { this.onConnect(); } if (packet.type == 'error' && packet.advice == 'reconnect') { this.isOpen = false; } this.socket.onPacket(packet); return this; }; /** * Sets close timeout * * @api private */ Transport.prototype.setCloseTimeout = function () { if (!this.closeTimeout) { var self = this; this.closeTimeout = setTimeout(function () { self.onDisconnect(); }, this.socket.closeTimeout); } }; /** * Called when transport disconnects. * * @api private */ Transport.prototype.onDisconnect = function () { if (this.isOpen) this.close(); this.clearTimeouts(); this.socket.onDisconnect(); return this; }; /** * Called when transport connects * * @api private */ Transport.prototype.onConnect = function () { this.socket.onConnect(); return this; }; /** * Clears close timeout * * @api private */ Transport.prototype.clearCloseTimeout = function () { if (this.closeTimeout) { clearTimeout(this.closeTimeout); this.closeTimeout = null; } }; /** * Clear timeouts * * @api private */ Transport.prototype.clearTimeouts = function () { this.clearCloseTimeout(); if (this.reopenTimeout) { clearTimeout(this.reopenTimeout); } }; /** * Sends a packet * * @param {Object} packet object. * @api private */ Transport.prototype.packet = function (packet) { this.send(io.parser.encodePacket(packet)); }; /** * Send the received heartbeat message back to server. So the server * knows we are still connected. * * @param {String} heartbeat Heartbeat response from the server. * @api private */ Transport.prototype.onHeartbeat = function (heartbeat) { this.packet({ type: 'heartbeat' }); }; /** * Called when the transport opens. * * @api private */ Transport.prototype.onOpen = function () { this.isOpen = true; this.clearCloseTimeout(); this.socket.onOpen(); }; /** * Notifies the base when the connection with the Socket.IO server * has been disconnected. * * @api private */ Transport.prototype.onClose = function () { var self = this; /* FIXME: reopen delay causing a infinit loop this.reopenTimeout = setTimeout(function () { self.open(); }, this.socket.options['reopen delay']);*/ this.isOpen = false; this.socket.onClose(); this.onDisconnect(); }; /** * Generates a connection url based on the Socket.IO URL Protocol. * See for more details. * * @returns {String} Connection url * @api private */ Transport.prototype.prepareUrl = function () { var options = this.socket.options; return this.scheme() + '://' + options.host + ':' + options.port + '/' + options.resource + '/' + io.protocol + '/' + this.name + '/' + this.sessid; }; /** * Checks if the transport is ready to start a connection. * * @param {Socket} socket The socket instance that needs a transport * @param {Function} fn The callback * @api private */ Transport.prototype.ready = function (socket, fn) { fn.call(this); }; })( 'undefined' != typeof io ? io : module.exports , 'undefined' != typeof io ? io : module.parent.exports ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io, global) { /** * Expose constructor. */ exports.Socket = Socket; /** * Create a new `Socket.IO client` which can establish a persistent * connection with a Socket.IO enabled server. * * @api public */ function Socket (options) { this.options = { port: 80 , secure: false , document: 'document' in global ? document : false , resource: 'socket.io' , transports: io.transports , 'connect timeout': 10000 , 'try multiple transports': true , 'reconnect': true , 'reconnection delay': 500 , 'reconnection limit': Infinity , 'reopen delay': 3000 , 'max reconnection attempts': 10 , 'sync disconnect on unload': false , 'auto connect': true , 'flash policy port': 10843 , 'manualFlush': false }; io.util.merge(this.options, options); this.connected = false; this.open = false; this.connecting = false; this.reconnecting = false; this.namespaces = {}; this.buffer = []; this.doBuffer = false; if (this.options['sync disconnect on unload'] && (!this.isXDomain() || io.util.ua.hasCORS)) { var self = this; io.util.on(global, 'beforeunload', function () { self.disconnectSync(); }, false); } if (this.options['auto connect']) { this.connect(); } }; /** * Apply EventEmitter mixin. */ io.util.mixin(Socket, io.EventEmitter); /** * Returns a namespace listener/emitter for this socket * * @api public */ Socket.prototype.of = function (name) { if (!this.namespaces[name]) { this.namespaces[name] = new io.SocketNamespace(this, name); if (name !== '') { this.namespaces[name].packet({ type: 'connect' }); } } return this.namespaces[name]; }; /** * Emits the given event to the Socket and all namespaces * * @api private */ Socket.prototype.publish = function () { this.emit.apply(this, arguments); var nsp; for (var i in this.namespaces) { if (this.namespaces.hasOwnProperty(i)) { nsp = this.of(i); nsp.$emit.apply(nsp, arguments); } } }; /** * Performs the handshake * * @api private */ function empty () { }; Socket.prototype.handshake = function (fn) { var self = this , options = this.options; function complete (data) { if (data instanceof Error) { self.connecting = false; self.onError(data.message); } else { fn.apply(null, data.split(':')); } }; var url = [ 'http' + (options.secure ? 's' : '') + ':/' , options.host + ':' + options.port , options.resource , io.protocol , io.util.query(this.options.query, 't=' + +new Date) ].join('/'); if (this.isXDomain() && !io.util.ua.hasCORS) { var insertAt = document.getElementsByTagName('script')[0] , script = document.createElement('script'); script.src = url + '&jsonp=' + io.j.length; insertAt.parentNode.insertBefore(script, insertAt); io.j.push(function (data) { complete(data); script.parentNode.removeChild(script); }); } else { var xhr = io.util.request(); xhr.open('GET', url, true); if (this.isXDomain()) { xhr.withCredentials = true; } xhr.onreadystatechange = function () { if (xhr.readyState == 4) { xhr.onreadystatechange = empty; if (xhr.status == 200) { complete(xhr.responseText); } else if (xhr.status == 403) { self.onError(xhr.responseText); } else { self.connecting = false; !self.reconnecting && self.onError(xhr.responseText); } } }; xhr.send(null); } }; /** * Find an available transport based on the options supplied in the constructor. * * @api private */ Socket.prototype.getTransport = function (override) { var transports = override || this.transports, match; for (var i = 0, transport; transport = transports[i]; i++) { if (io.Transport[transport] && io.Transport[transport].check(this) && (!this.isXDomain() || io.Transport[transport].xdomainCheck(this))) { return new io.Transport[transport](this, this.sessionid); } } return null; }; /** * Connects to the server. * * @param {Function} [fn] Callback. * @returns {io.Socket} * @api public */ Socket.prototype.connect = function (fn) { if (this.connecting) { return this; } var self = this; self.connecting = true; this.handshake(function (sid, heartbeat, close, transports) { self.sessionid = sid; self.closeTimeout = close * 1000; self.heartbeatTimeout = heartbeat * 1000; if(!self.transports) self.transports = self.origTransports = (transports ? io.util.intersect( transports.split(',') , self.options.transports ) : self.options.transports); self.setHeartbeatTimeout(); function connect (transports){ if (self.transport) self.transport.clearTimeouts(); self.transport = self.getTransport(transports); if (!self.transport) return self.publish('connect_failed'); // once the transport is ready self.transport.ready(self, function () { self.connecting = true; self.publish('connecting', self.transport.name); self.transport.open(); if (self.options['connect timeout']) { self.connectTimeoutTimer = setTimeout(function () { if (!self.connected) { self.connecting = false; if (self.options['try multiple transports']) { var remaining = self.transports; while (remaining.length > 0 && remaining.splice(0,1)[0] != self.transport.name) {} if (remaining.length){ connect(remaining); } else { self.publish('connect_failed'); } } } }, self.options['connect timeout']); } }); } connect(self.transports); self.once('connect', function (){ clearTimeout(self.connectTimeoutTimer); fn && typeof fn == 'function' && fn(); }); }); return this; }; /** * Clears and sets a new heartbeat timeout using the value given by the * server during the handshake. * * @api private */ Socket.prototype.setHeartbeatTimeout = function () { clearTimeout(this.heartbeatTimeoutTimer); if(this.transport && !this.transport.heartbeats()) return; var self = this; this.heartbeatTimeoutTimer = setTimeout(function () { self.transport.onClose(); }, this.heartbeatTimeout); }; /** * Sends a message. * * @param {Object} data packet. * @returns {io.Socket} * @api public */ Socket.prototype.packet = function (data) { if (this.connected && !this.doBuffer) { this.transport.packet(data); } else { this.buffer.push(data); } return this; }; /** * Sets buffer state * * @api private */ Socket.prototype.setBuffer = function (v) { this.doBuffer = v; if (!v && this.connected && this.buffer.length) { if (!this.options['manualFlush']) { this.flushBuffer(); } } }; /** * Flushes the buffer data over the wire. * To be invoked manually when 'manualFlush' is set to true. * * @api public */ Socket.prototype.flushBuffer = function() { this.transport.payload(this.buffer); this.buffer = []; }; /** * Disconnect the established connect. * * @returns {io.Socket} * @api public */ Socket.prototype.disconnect = function () { if (this.connected || this.connecting) { if (this.open) { this.of('').packet({ type: 'disconnect' }); } // handle disconnection immediately this.onDisconnect('booted'); } return this; }; /** * Disconnects the socket with a sync XHR. * * @api private */ Socket.prototype.disconnectSync = function () { // ensure disconnection var xhr = io.util.request(); var uri = [ 'http' + (this.options.secure ? 's' : '') + ':/' , this.options.host + ':' + this.options.port , this.options.resource , io.protocol , '' , this.sessionid ].join('/') + '/?disconnect=1'; xhr.open('GET', uri, false); xhr.send(null); // handle disconnection immediately this.onDisconnect('booted'); }; /** * Check if we need to use cross domain enabled transports. Cross domain would * be a different port or different domain name. * * @returns {Boolean} * @api private */ Socket.prototype.isXDomain = function () { var port = global.location.port || ('https:' == global.location.protocol ? 443 : 80); return this.options.host !== global.location.hostname || this.options.port != port; }; /** * Called upon handshake. * * @api private */ Socket.prototype.onConnect = function () { if (!this.connected) { this.connected = true; this.connecting = false; if (!this.doBuffer) { // make sure to flush the buffer this.setBuffer(false); } this.emit('connect'); } }; /** * Called when the transport opens * * @api private */ Socket.prototype.onOpen = function () { this.open = true; }; /** * Called when the transport closes. * * @api private */ Socket.prototype.onClose = function () { this.open = false; clearTimeout(this.heartbeatTimeoutTimer); }; /** * Called when the transport first opens a connection * * @param text */ Socket.prototype.onPacket = function (packet) { this.of(packet.endpoint).onPacket(packet); }; /** * Handles an error. * * @api private */ Socket.prototype.onError = function (err) { if (err && err.advice) { if (err.advice === 'reconnect' && (this.connected || this.connecting)) { this.disconnect(); if (this.options.reconnect) { this.reconnect(); } } } this.publish('error', err && err.reason ? err.reason : err); }; /** * Called when the transport disconnects. * * @api private */ Socket.prototype.onDisconnect = function (reason) { var wasConnected = this.connected , wasConnecting = this.connecting; this.connected = false; this.connecting = false; this.open = false; if (wasConnected || wasConnecting) { this.transport.close(); this.transport.clearTimeouts(); if (wasConnected) { this.publish('disconnect', reason); if ('booted' != reason && this.options.reconnect && !this.reconnecting) { this.reconnect(); } } } }; /** * Called upon reconnection. * * @api private */ Socket.prototype.reconnect = function () { this.reconnecting = true; this.reconnectionAttempts = 0; this.reconnectionDelay = this.options['reconnection delay']; var self = this , maxAttempts = this.options['max reconnection attempts'] , tryMultiple = this.options['try multiple transports'] , limit = this.options['reconnection limit']; function reset () { if (self.connected) { for (var i in self.namespaces) { if (self.namespaces.hasOwnProperty(i) && '' !== i) { self.namespaces[i].packet({ type: 'connect' }); } } self.publish('reconnect', self.transport.name, self.reconnectionAttempts); } clearTimeout(self.reconnectionTimer); self.removeListener('connect_failed', maybeReconnect); self.removeListener('connect', maybeReconnect); self.reconnecting = false; delete self.reconnectionAttempts; delete self.reconnectionDelay; delete self.reconnectionTimer; delete self.redoTransports; self.options['try multiple transports'] = tryMultiple; }; function maybeReconnect () { if (!self.reconnecting) { return; } if (self.connected) { return reset(); }; if (self.connecting && self.reconnecting) { return self.reconnectionTimer = setTimeout(maybeReconnect, 1000); } if (self.reconnectionAttempts++ >= maxAttempts) { if (!self.redoTransports) { self.on('connect_failed', maybeReconnect); self.options['try multiple transports'] = true; self.transports = self.origTransports; self.transport = self.getTransport(); self.redoTransports = true; self.connect(); } else { self.publish('reconnect_failed'); reset(); } } else { if (self.reconnectionDelay < limit) { self.reconnectionDelay *= 2; // exponential back off } self.connect(); self.publish('reconnecting', self.reconnectionDelay, self.reconnectionAttempts); self.reconnectionTimer = setTimeout(maybeReconnect, self.reconnectionDelay); } }; this.options['try multiple transports'] = false; this.reconnectionTimer = setTimeout(maybeReconnect, this.reconnectionDelay); this.on('connect', maybeReconnect); }; })( 'undefined' != typeof io ? io : module.exports , 'undefined' != typeof io ? io : module.parent.exports , this ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io) { /** * Expose constructor. */ exports.SocketNamespace = SocketNamespace; /** * Socket namespace constructor. * * @constructor * @api public */ function SocketNamespace (socket, name) { this.socket = socket; this.name = name || ''; this.flags = {}; this.json = new Flag(this, 'json'); this.ackPackets = 0; this.acks = {}; }; /** * Apply EventEmitter mixin. */ io.util.mixin(SocketNamespace, io.EventEmitter); /** * Copies emit since we override it * * @api private */ SocketNamespace.prototype.$emit = io.EventEmitter.prototype.emit; /** * Creates a new namespace, by proxying the request to the socket. This * allows us to use the synax as we do on the server. * * @api public */ SocketNamespace.prototype.of = function () { return this.socket.of.apply(this.socket, arguments); }; /** * Sends a packet. * * @api private */ SocketNamespace.prototype.packet = function (packet) { packet.endpoint = this.name; this.socket.packet(packet); this.flags = {}; return this; }; /** * Sends a message * * @api public */ SocketNamespace.prototype.send = function (data, fn) { var packet = { type: this.flags.json ? 'json' : 'message' , data: data }; if ('function' == typeof fn) { packet.id = ++this.ackPackets; packet.ack = true; this.acks[packet.id] = fn; } return this.packet(packet); }; /** * Emits an event * * @api public */ SocketNamespace.prototype.emit = function (name) { var args = Array.prototype.slice.call(arguments, 1) , lastArg = args[args.length - 1] , packet = { type: 'event' , name: name }; if ('function' == typeof lastArg) { packet.id = ++this.ackPackets; packet.ack = 'data'; this.acks[packet.id] = lastArg; args = args.slice(0, args.length - 1); } packet.args = args; return this.packet(packet); }; /** * Disconnects the namespace * * @api private */ SocketNamespace.prototype.disconnect = function () { if (this.name === '') { this.socket.disconnect(); } else { this.packet({ type: 'disconnect' }); this.$emit('disconnect'); } return this; }; /** * Handles a packet * * @api private */ SocketNamespace.prototype.onPacket = function (packet) { var self = this; function ack () { self.packet({ type: 'ack' , args: io.util.toArray(arguments) , ackId: packet.id }); }; switch (packet.type) { case 'connect': this.$emit('connect'); break; case 'disconnect': if (this.name === '') { this.socket.onDisconnect(packet.reason || 'booted'); } else { this.$emit('disconnect', packet.reason); } break; case 'message': case 'json': var params = ['message', packet.data]; if (packet.ack == 'data') { params.push(ack); } else if (packet.ack) { this.packet({ type: 'ack', ackId: packet.id }); } this.$emit.apply(this, params); break; case 'event': var params = [packet.name].concat(packet.args); if (packet.ack == 'data') params.push(ack); this.$emit.apply(this, params); break; case 'ack': if (this.acks[packet.ackId]) { this.acks[packet.ackId].apply(this, packet.args); delete this.acks[packet.ackId]; } break; case 'error': if (packet.advice){ this.socket.onError(packet); } else { if (packet.reason == 'unauthorized') { this.$emit('connect_failed', packet.reason); } else { this.$emit('error', packet.reason); } } break; } }; /** * Flag interface. * * @api private */ function Flag (nsp, name) { this.namespace = nsp; this.name = name; }; /** * Send a message * * @api public */ Flag.prototype.send = function () { this.namespace.flags[this.name] = true; this.namespace.send.apply(this.namespace, arguments); }; /** * Emit an event * * @api public */ Flag.prototype.emit = function () { this.namespace.flags[this.name] = true; this.namespace.emit.apply(this.namespace, arguments); }; })( 'undefined' != typeof io ? io : module.exports , 'undefined' != typeof io ? io : module.parent.exports ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io, global) { /** * Expose constructor. */ exports.websocket = WS; /** * The WebSocket transport uses the HTML5 WebSocket API to establish an * persistent connection with the Socket.IO server. This transport will also * be inherited by the FlashSocket fallback as it provides a API compatible * polyfill for the WebSockets. * * @constructor * @extends {io.Transport} * @api public */ function WS (socket) { io.Transport.apply(this, arguments); }; /** * Inherits from Transport. */ io.util.inherit(WS, io.Transport); /** * Transport name * * @api public */ WS.prototype.name = 'websocket'; /** * Initializes a new `WebSocket` connection with the Socket.IO server. We attach * all the appropriate listeners to handle the responses from the server. * * @returns {Transport} * @api public */ WS.prototype.open = function () { var query = io.util.query(this.socket.options.query) , self = this , Socket if (!Socket) { Socket = global.MozWebSocket || global.WebSocket; } this.websocket = new Socket(this.prepareUrl() + query); this.websocket.onopen = function () { self.onOpen(); self.socket.setBuffer(false); }; this.websocket.onmessage = function (ev) { self.onData(ev.data); }; this.websocket.onclose = function () { self.onClose(); self.socket.setBuffer(true); }; this.websocket.onerror = function (e) { self.onError(e); }; return this; }; /** * Send a message to the Socket.IO server. The message will automatically be * encoded in the correct message format. * * @returns {Transport} * @api public */ // Do to a bug in the current IDevices browser, we need to wrap the send in a // setTimeout, when they resume from sleeping the browser will crash if // we don't allow the browser time to detect the socket has been closed if (io.util.ua.iDevice) { WS.prototype.send = function (data) { var self = this; setTimeout(function() { self.websocket.send(data); },0); return this; }; } else { WS.prototype.send = function (data) { this.websocket.send(data); return this; }; } /** * Payload * * @api private */ WS.prototype.payload = function (arr) { for (var i = 0, l = arr.length; i < l; i++) { this.packet(arr[i]); } return this; }; /** * Disconnect the established `WebSocket` connection. * * @returns {Transport} * @api public */ WS.prototype.close = function () { this.websocket.close(); return this; }; /** * Handle the errors that `WebSocket` might be giving when we * are attempting to connect or send messages. * * @param {Error} e The error. * @api private */ WS.prototype.onError = function (e) { this.socket.onError(e); }; /** * Returns the appropriate scheme for the URI generation. * * @api private */ WS.prototype.scheme = function () { return this.socket.options.secure ? 'wss' : 'ws'; }; /** * Checks if the browser has support for native `WebSockets` and that * it's not the polyfill created for the FlashSocket transport. * * @return {Boolean} * @api public */ WS.check = function () { return ('WebSocket' in global && !('__addTask' in WebSocket)) || 'MozWebSocket' in global; }; /** * Check if the `WebSocket` transport support cross domain communications. * * @returns {Boolean} * @api public */ WS.xdomainCheck = function () { return true; }; /** * Add the transport to your public io.transports array. * * @api private */ io.transports.push('websocket'); })( 'undefined' != typeof io ? io.Transport : module.exports , 'undefined' != typeof io ? io : module.parent.exports , this ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io) { /** * Expose constructor. */ exports.flashsocket = Flashsocket; /** * The FlashSocket transport. This is a API wrapper for the HTML5 WebSocket * specification. It uses a .swf file to communicate with the server. If you want * to serve the .swf file from a other server than where the Socket.IO script is * coming from you need to use the insecure version of the .swf. More information * about this can be found on the github page. * * @constructor * @extends {io.Transport.websocket} * @api public */ function Flashsocket () { io.Transport.websocket.apply(this, arguments); }; /** * Inherits from Transport. */ io.util.inherit(Flashsocket, io.Transport.websocket); /** * Transport name * * @api public */ Flashsocket.prototype.name = 'flashsocket'; /** * Disconnect the established `FlashSocket` connection. This is done by adding a * new task to the FlashSocket. The rest will be handled off by the `WebSocket` * transport. * * @returns {Transport} * @api public */ Flashsocket.prototype.open = function () { var self = this , args = arguments; WebSocket.__addTask(function () { io.Transport.websocket.prototype.open.apply(self, args); }); return this; }; /** * Sends a message to the Socket.IO server. This is done by adding a new * task to the FlashSocket. The rest will be handled off by the `WebSocket` * transport. * * @returns {Transport} * @api public */ Flashsocket.prototype.send = function () { var self = this, args = arguments; WebSocket.__addTask(function () { io.Transport.websocket.prototype.send.apply(self, args); }); return this; }; /** * Disconnects the established `FlashSocket` connection. * * @returns {Transport} * @api public */ Flashsocket.prototype.close = function () { WebSocket.__tasks.length = 0; io.Transport.websocket.prototype.close.call(this); return this; }; /** * The WebSocket fall back needs to append the flash container to the body * element, so we need to make sure we have access to it. Or defer the call * until we are sure there is a body element. * * @param {Socket} socket The socket instance that needs a transport * @param {Function} fn The callback * @api private */ Flashsocket.prototype.ready = function (socket, fn) { function init () { var options = socket.options , port = options['flash policy port'] , path = [ 'http' + (options.secure ? 's' : '') + ':/' , options.host + ':' + options.port , options.resource , 'static/flashsocket' , 'WebSocketMain' + (socket.isXDomain() ? 'Insecure' : '') + '.swf' ]; // Only start downloading the swf file when the checked that this browser // actually supports it if (!Flashsocket.loaded) { if (typeof WEB_SOCKET_SWF_LOCATION === 'undefined') { // Set the correct file based on the XDomain settings WEB_SOCKET_SWF_LOCATION = path.join('/'); } if (port !== 843) { WebSocket.loadFlashPolicyFile('xmlsocket://' + options.host + ':' + port); } WebSocket.__initialize(); Flashsocket.loaded = true; } fn.call(self); } var self = this; if (document.body) return init(); io.util.load(init); }; /** * Check if the FlashSocket transport is supported as it requires that the Adobe * Flash Player plug-in version `10.0.0` or greater is installed. And also check if * the polyfill is correctly loaded. * * @returns {Boolean} * @api public */ Flashsocket.check = function () { if ( typeof WebSocket == 'undefined' || !('__initialize' in WebSocket) || !swfobject ) return false; return swfobject.getFlashPlayerVersion().major >= 10; }; /** * Check if the FlashSocket transport can be used as cross domain / cross origin * transport. Because we can't see which type (secure or insecure) of .swf is used * we will just return true. * * @returns {Boolean} * @api public */ Flashsocket.xdomainCheck = function () { return true; }; /** * Disable AUTO_INITIALIZATION */ if (typeof window != 'undefined') { WEB_SOCKET_DISABLE_AUTO_INITIALIZATION = true; } /** * Add the transport to your public io.transports array. * * @api private */ io.transports.push('flashsocket'); })( 'undefined' != typeof io ? io.Transport : module.exports , 'undefined' != typeof io ? io : module.parent.exports ); /* SWFObject v2.2 is released under the MIT License */ if ('undefined' != typeof window) { 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 // License: New BSD License // Reference: http://dev.w3.org/html5/websockets/ // Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol (function() { if ('undefined' == typeof window || window.WebSocket) return; var console = window.console; if (!console || !console.log || !console.error) { console = {log: function(){ }, error: function(){ }}; } if (!swfobject.hasFlashPlayerVersion("10.0.0")) { console.error("Flash Player >= 10.0.0 is required."); return; } if (location.protocol == "file:") { console.error( "WARNING: web-socket-js doesn't work in file:///... URL " + "unless you set Flash Security Settings properly. " + "Open the page via Web server i.e. http://..."); } /** * This class represents a faux web socket. * @param {string} url * @param {array or string} protocols * @param {string} proxyHost * @param {int} proxyPort * @param {string} headers */ WebSocket = function(url, protocols, proxyHost, proxyPort, headers) { var self = this; self.__id = WebSocket.__nextId++; WebSocket.__instances[self.__id] = self; self.readyState = WebSocket.CONNECTING; self.bufferedAmount = 0; self.__events = {}; if (!protocols) { protocols = []; } else if (typeof protocols == "string") { protocols = [protocols]; } // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc. // Otherwise, when onopen fires immediately, onopen is called before it is set. setTimeout(function() { WebSocket.__addTask(function() { WebSocket.__flash.create( self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null); }); }, 0); }; /** * Send data to the web socket. * @param {string} data The data to send to the socket. * @return {boolean} True for success, false for failure. */ WebSocket.prototype.send = function(data) { if (this.readyState == WebSocket.CONNECTING) { throw "INVALID_STATE_ERR: Web Socket connection has not been established"; } // We use encodeURIComponent() here, because FABridge doesn't work if // the argument includes some characters. We don't use escape() here // because of this: // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't // preserve all Unicode characters either e.g. "\uffff" in Firefox. // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require // additional testing. var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data)); if (result < 0) { // success return true; } else { this.bufferedAmount += result; return false; } }; /** * Close this web socket gracefully. */ WebSocket.prototype.close = function() { if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) { return; } this.readyState = WebSocket.CLOSING; WebSocket.__flash.close(this.__id); }; /** * Implementation of {@link DOM 2 EventTarget Interface} * * @param {string} type * @param {function} listener * @param {boolean} useCapture * @return void */ WebSocket.prototype.addEventListener = function(type, listener, useCapture) { if (!(type in this.__events)) { this.__events[type] = []; } this.__events[type].push(listener); }; /** * Implementation of {@link DOM 2 EventTarget Interface} * * @param {string} type * @param {function} listener * @param {boolean} useCapture * @return void */ WebSocket.prototype.removeEventListener = function(type, listener, useCapture) { if (!(type in this.__events)) return; var events = this.__events[type]; for (var i = events.length - 1; i >= 0; --i) { if (events[i] === listener) { events.splice(i, 1); break; } } }; /** * Implementation of {@link DOM 2 EventTarget Interface} * * @param {Event} event * @return void */ WebSocket.prototype.dispatchEvent = function(event) { var events = this.__events[event.type] || []; for (var i = 0; i < events.length; ++i) { events[i](event); } var handler = this["on" + event.type]; if (handler) handler(event); }; /** * Handles an event from Flash. * @param {Object} flashEvent */ WebSocket.prototype.__handleEvent = function(flashEvent) { if ("readyState" in flashEvent) { this.readyState = flashEvent.readyState; } if ("protocol" in flashEvent) { this.protocol = flashEvent.protocol; } var jsEvent; if (flashEvent.type == "open" || flashEvent.type == "error") { jsEvent = this.__createSimpleEvent(flashEvent.type); } else if (flashEvent.type == "close") { // TODO implement jsEvent.wasClean jsEvent = this.__createSimpleEvent("close"); } else if (flashEvent.type == "message") { var data = decodeURIComponent(flashEvent.message); jsEvent = this.__createMessageEvent("message", data); } else { throw "unknown event type: " + flashEvent.type; } this.dispatchEvent(jsEvent); }; WebSocket.prototype.__createSimpleEvent = function(type) { if (document.createEvent && window.Event) { var event = document.createEvent("Event"); event.initEvent(type, false, false); return event; } else { return {type: type, bubbles: false, cancelable: false}; } }; WebSocket.prototype.__createMessageEvent = function(type, data) { if (document.createEvent && window.MessageEvent && !window.opera) { var event = document.createEvent("MessageEvent"); event.initMessageEvent("message", false, false, data, null, null, window, null); return event; } else { // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes. return {type: type, data: data, bubbles: false, cancelable: false}; } }; /** * Define the WebSocket readyState enumeration. */ WebSocket.CONNECTING = 0; WebSocket.OPEN = 1; WebSocket.CLOSING = 2; WebSocket.CLOSED = 3; WebSocket.__flash = null; WebSocket.__instances = {}; WebSocket.__tasks = []; WebSocket.__nextId = 0; /** * Load a new flash security policy file. * @param {string} url */ WebSocket.loadFlashPolicyFile = function(url){ WebSocket.__addTask(function() { WebSocket.__flash.loadManualPolicyFile(url); }); }; /** * Loads WebSocketMain.swf and creates WebSocketMain object in Flash. */ WebSocket.__initialize = function() { if (WebSocket.__flash) return; if (WebSocket.__swfLocation) { // For backword compatibility. window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation; } if (!window.WEB_SOCKET_SWF_LOCATION) { console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); return; } var container = document.createElement("div"); container.id = "webSocketContainer"; // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents // Flash from loading at least in IE. So we move it out of the screen at (-100, -100). // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is // the best we can do as far as we know now. container.style.position = "absolute"; if (WebSocket.__isFlashLite()) { container.style.left = "0px"; container.style.top = "0px"; } else { container.style.left = "-100px"; container.style.top = "-100px"; } var holder = document.createElement("div"); holder.id = "webSocketFlash"; container.appendChild(holder); document.body.appendChild(container); // See this article for hasPriority: // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html swfobject.embedSWF( WEB_SOCKET_SWF_LOCATION, "webSocketFlash", "1" /* width */, "1" /* height */, "10.0.0" /* SWF version */, null, null, {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"}, null, function(e) { if (!e.success) { console.error("[WebSocket] swfobject.embedSWF failed"); } }); }; /** * Called by Flash to notify JS that it's fully loaded and ready * for communication. */ WebSocket.__onFlashInitialized = function() { // We need to set a timeout here to avoid round-trip calls // to flash during the initialization process. setTimeout(function() { WebSocket.__flash = document.getElementById("webSocketFlash"); WebSocket.__flash.setCallerUrl(location.href); WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG); for (var i = 0; i < WebSocket.__tasks.length; ++i) { WebSocket.__tasks[i](); } WebSocket.__tasks = []; }, 0); }; /** * Called by Flash to notify WebSockets events are fired. */ WebSocket.__onFlashEvent = function() { setTimeout(function() { try { // Gets events using receiveEvents() instead of getting it from event object // of Flash event. This is to make sure to keep message order. // It seems sometimes Flash events don't arrive in the same order as they are sent. var events = WebSocket.__flash.receiveEvents(); for (var i = 0; i < events.length; ++i) { WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]); } } catch (e) { console.error(e); } }, 0); return true; }; // Called by Flash. WebSocket.__log = function(message) { console.log(decodeURIComponent(message)); }; // Called by Flash. WebSocket.__error = function(message) { console.error(decodeURIComponent(message)); }; WebSocket.__addTask = function(task) { if (WebSocket.__flash) { task(); } else { WebSocket.__tasks.push(task); } }; /** * Test if the browser is running flash lite. * @return {boolean} True if flash lite is running, false otherwise. */ WebSocket.__isFlashLite = function() { if (!window.navigator || !window.navigator.mimeTypes) { return false; } var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"]; if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) { return false; } return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false; }; if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) { if (window.addEventListener) { window.addEventListener("load", function(){ WebSocket.__initialize(); }, false); } else { window.attachEvent("onload", function(){ WebSocket.__initialize(); }); } } })(); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io, global) { /** * Expose constructor. * * @api public */ exports.XHR = XHR; /** * XHR constructor * * @costructor * @api public */ function XHR (socket) { if (!socket) return; io.Transport.apply(this, arguments); this.sendBuffer = []; }; /** * Inherits from Transport. */ io.util.inherit(XHR, io.Transport); /** * Establish a connection * * @returns {Transport} * @api public */ XHR.prototype.open = function () { this.socket.setBuffer(false); this.onOpen(); this.get(); // we need to make sure the request succeeds since we have no indication // whether the request opened or not until it succeeded. this.setCloseTimeout(); return this; }; /** * Check if we need to send data to the Socket.IO server, if we have data in our * buffer we encode it and forward it to the `post` method. * * @api private */ XHR.prototype.payload = function (payload) { var msgs = []; for (var i = 0, l = payload.length; i < l; i++) { msgs.push(io.parser.encodePacket(payload[i])); } this.send(io.parser.encodePayload(msgs)); }; /** * Send data to the Socket.IO server. * * @param data The message * @returns {Transport} * @api public */ XHR.prototype.send = function (data) { this.post(data); return this; }; /** * Posts a encoded message to the Socket.IO server. * * @param {String} data A encoded message. * @api private */ function empty () { }; XHR.prototype.post = function (data) { var self = this; this.socket.setBuffer(true); function stateChange () { if (this.readyState == 4) { this.onreadystatechange = empty; self.posting = false; if (this.status == 200){ self.socket.setBuffer(false); } else { self.onClose(); } } } function onload () { this.onload = empty; self.socket.setBuffer(false); }; this.sendXHR = this.request('POST'); if (global.XDomainRequest && this.sendXHR instanceof XDomainRequest) { this.sendXHR.onload = this.sendXHR.onerror = onload; } else { this.sendXHR.onreadystatechange = stateChange; } this.sendXHR.send(data); }; /** * Disconnects the established `XHR` connection. * * @returns {Transport} * @api public */ XHR.prototype.close = function () { this.onClose(); return this; }; /** * Generates a configured XHR request * * @param {String} url The url that needs to be requested. * @param {String} method The method the request should use. * @returns {XMLHttpRequest} * @api private */ XHR.prototype.request = function (method) { var req = io.util.request(this.socket.isXDomain()) , query = io.util.query(this.socket.options.query, 't=' + +new Date); req.open(method || 'GET', this.prepareUrl() + query, true); if (method == 'POST') { try { if (req.setRequestHeader) { req.setRequestHeader('Content-type', 'text/plain;charset=UTF-8'); } else { // XDomainRequest req.contentType = 'text/plain'; } } catch (e) {} } return req; }; /** * Returns the scheme to use for the transport URLs. * * @api private */ XHR.prototype.scheme = function () { return this.socket.options.secure ? 'https' : 'http'; }; /** * Check if the XHR transports are supported * * @param {Boolean} xdomain Check if we support cross domain requests. * @returns {Boolean} * @api public */ XHR.check = function (socket, xdomain) { try { var request = io.util.request(xdomain), usesXDomReq = (global.XDomainRequest && request instanceof XDomainRequest), socketProtocol = (socket && socket.options && socket.options.secure ? 'https:' : 'http:'), isXProtocol = (global.location && socketProtocol != global.location.protocol); if (request && !(usesXDomReq && isXProtocol)) { return true; } } catch(e) {} return false; }; /** * Check if the XHR transport supports cross domain requests. * * @returns {Boolean} * @api public */ XHR.xdomainCheck = function (socket) { return XHR.check(socket, true); }; })( 'undefined' != typeof io ? io.Transport : module.exports , 'undefined' != typeof io ? io : module.parent.exports , this ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io) { /** * Expose constructor. */ exports.htmlfile = HTMLFile; /** * The HTMLFile transport creates a `forever iframe` based transport * for Internet Explorer. Regular forever iframe implementations will * continuously trigger the browsers buzy indicators. If the forever iframe * is created inside a `htmlfile` these indicators will not be trigged. * * @constructor * @extends {io.Transport.XHR} * @api public */ function HTMLFile (socket) { io.Transport.XHR.apply(this, arguments); }; /** * Inherits from XHR transport. */ io.util.inherit(HTMLFile, io.Transport.XHR); /** * Transport name * * @api public */ HTMLFile.prototype.name = 'htmlfile'; /** * Creates a new Ac...eX `htmlfile` with a forever loading iframe * that can be used to listen to messages. Inside the generated * `htmlfile` a reference will be made to the HTMLFile transport. * * @api private */ HTMLFile.prototype.get = function () { this.doc = new window[(['Active'].concat('Object').join('X'))]('htmlfile'); this.doc.open(); this.doc.write(''); this.doc.close(); this.doc.parentWindow.s = this; var iframeC = this.doc.createElement('div'); iframeC.className = 'socketio'; this.doc.body.appendChild(iframeC); this.iframe = this.doc.createElement('iframe'); iframeC.appendChild(this.iframe); var self = this , query = io.util.query(this.socket.options.query, 't='+ +new Date); this.iframe.src = this.prepareUrl() + query; io.util.on(window, 'unload', function () { self.destroy(); }); }; /** * The Socket.IO server will write script tags inside the forever * iframe, this function will be used as callback for the incoming * information. * * @param {String} data The message * @param {document} doc Reference to the context * @api private */ HTMLFile.prototype._ = function (data, doc) { // unescape all forward slashes. see GH-1251 data = data.replace(/\\\//g, '/'); this.onData(data); try { var script = doc.getElementsByTagName('script')[0]; script.parentNode.removeChild(script); } catch (e) { } }; /** * Destroy the established connection, iframe and `htmlfile`. * And calls the `CollectGarbage` function of Internet Explorer * to release the memory. * * @api private */ HTMLFile.prototype.destroy = function () { if (this.iframe){ try { this.iframe.src = 'about:blank'; } catch(e){} this.doc = null; this.iframe.parentNode.removeChild(this.iframe); this.iframe = null; CollectGarbage(); } }; /** * Disconnects the established connection. * * @returns {Transport} Chaining. * @api public */ HTMLFile.prototype.close = function () { this.destroy(); return io.Transport.XHR.prototype.close.call(this); }; /** * Checks if the browser supports this transport. The browser * must have an `Ac...eXObject` implementation. * * @return {Boolean} * @api public */ HTMLFile.check = function (socket) { if (typeof window != "undefined" && (['Active'].concat('Object').join('X')) in window){ try { var a = new window[(['Active'].concat('Object').join('X'))]('htmlfile'); return a && io.Transport.XHR.check(socket); } catch(e){} } return false; }; /** * Check if cross domain requests are supported. * * @returns {Boolean} * @api public */ HTMLFile.xdomainCheck = function () { // we can probably do handling for sub-domains, we should // test that it's cross domain but a subdomain here return false; }; /** * Add the transport to your public io.transports array. * * @api private */ io.transports.push('htmlfile'); })( 'undefined' != typeof io ? io.Transport : module.exports , 'undefined' != typeof io ? io : module.parent.exports ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io, global) { /** * Expose constructor. */ exports['xhr-polling'] = XHRPolling; /** * The XHR-polling transport uses long polling XHR requests to create a * "persistent" connection with the server. * * @constructor * @api public */ function XHRPolling () { io.Transport.XHR.apply(this, arguments); }; /** * Inherits from XHR transport. */ io.util.inherit(XHRPolling, io.Transport.XHR); /** * Merge the properties from XHR transport */ io.util.merge(XHRPolling, io.Transport.XHR); /** * Transport name * * @api public */ XHRPolling.prototype.name = 'xhr-polling'; /** * Indicates whether heartbeats is enabled for this transport * * @api private */ XHRPolling.prototype.heartbeats = function () { return false; }; /** * Establish a connection, for iPhone and Android this will be done once the page * is loaded. * * @returns {Transport} Chaining. * @api public */ XHRPolling.prototype.open = function () { var self = this; io.Transport.XHR.prototype.open.call(self); return false; }; /** * Starts a XHR request to wait for incoming messages. * * @api private */ function empty () {}; XHRPolling.prototype.get = function () { if (!this.isOpen) return; var self = this; function stateChange () { if (this.readyState == 4) { this.onreadystatechange = empty; if (this.status == 200) { self.onData(this.responseText); self.get(); } else { self.onClose(); } } }; function onload () { this.onload = empty; this.onerror = empty; self.retryCounter = 1; self.onData(this.responseText); self.get(); }; function onerror () { self.retryCounter ++; if(!self.retryCounter || self.retryCounter > 3) { self.onClose(); } else { self.get(); } }; this.xhr = this.request(); if (global.XDomainRequest && this.xhr instanceof XDomainRequest) { this.xhr.onload = onload; this.xhr.onerror = onerror; } else { this.xhr.onreadystatechange = stateChange; } this.xhr.send(null); }; /** * Handle the unclean close behavior. * * @api private */ XHRPolling.prototype.onClose = function () { io.Transport.XHR.prototype.onClose.call(this); if (this.xhr) { this.xhr.onreadystatechange = this.xhr.onload = this.xhr.onerror = empty; try { this.xhr.abort(); } catch(e){} this.xhr = null; } }; /** * Webkit based browsers show a infinit spinner when you start a XHR request * before the browsers onload event is called so we need to defer opening of * the transport until the onload event is called. Wrapping the cb in our * defer method solve this. * * @param {Socket} socket The socket instance that needs a transport * @param {Function} fn The callback * @api private */ XHRPolling.prototype.ready = function (socket, fn) { var self = this; io.util.defer(function () { fn.call(self); }); }; /** * Add the transport to your public io.transports array. * * @api private */ io.transports.push('xhr-polling'); })( 'undefined' != typeof io ? io.Transport : module.exports , 'undefined' != typeof io ? io : module.parent.exports , this ); /** * socket.io * Copyright(c) 2011 LearnBoost * MIT Licensed */ (function (exports, io, global) { /** * There is a way to hide the loading indicator in Firefox. If you create and * remove a iframe it will stop showing the current loading indicator. * Unfortunately we can't feature detect that and UA sniffing is evil. * * @api private */ var indicator = global.document && "MozAppearance" in global.document.documentElement.style; /** * Expose constructor. */ exports['jsonp-polling'] = JSONPPolling; /** * The JSONP transport creates an persistent connection by dynamically * inserting a script tag in the page. This script tag will receive the * information of the Socket.IO server. When new information is received * it creates a new script tag for the new data stream. * * @constructor * @extends {io.Transport.xhr-polling} * @api public */ function JSONPPolling (socket) { io.Transport['xhr-polling'].apply(this, arguments); this.index = io.j.length; var self = this; io.j.push(function (msg) { self._(msg); }); }; /** * Inherits from XHR polling transport. */ io.util.inherit(JSONPPolling, io.Transport['xhr-polling']); /** * Transport name * * @api public */ JSONPPolling.prototype.name = 'jsonp-polling'; /** * Posts a encoded message to the Socket.IO server using an iframe. * The iframe is used because script tags can create POST based requests. * The iframe is positioned outside of the view so the user does not * notice it's existence. * * @param {String} data A encoded message. * @api private */ JSONPPolling.prototype.post = function (data) { var self = this , query = io.util.query( this.socket.options.query , 't='+ (+new Date) + '&i=' + this.index ); if (!this.form) { var form = document.createElement('form') , area = document.createElement('textarea') , id = this.iframeId = 'socketio_iframe_' + this.index , iframe; form.className = 'socketio'; form.style.position = 'absolute'; form.style.top = '0px'; form.style.left = '0px'; form.style.display = 'none'; form.target = id; form.method = 'POST'; form.setAttribute('accept-charset', 'utf-8'); area.name = 'd'; form.appendChild(area); document.body.appendChild(form); this.form = form; this.area = area; } this.form.action = this.prepareUrl() + query; function complete () { initIframe(); self.socket.setBuffer(false); }; function initIframe () { if (self.iframe) { self.form.removeChild(self.iframe); } try { // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) iframe = document.createElement('