diff --git a/.gitignore b/.gitignore
index 7b8da97..a54ed2e 100755
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,5 @@ IGNORE
 ORIG
 SUBMIT
 doc/_build/
+node_modules
+package-lock.json
\ No newline at end of file
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 0000000..19689db
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,570 @@
+[MASTER]
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code.
+extension-pkg-whitelist=
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS
+
+# Add files or directories matching the regex patterns to the blacklist. The
+# regex matches against base names, not paths.
+ignore-patterns=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
+# number of processors available to use.
+jobs=1
+
+# Control the amount of potential inferred values when inferring a single
+# object. This can help the performance when dealing with large functions or
+# complex, nested conditions.
+limit-inference-results=100
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# Specify a configuration file.
+#rcfile=
+
+# When enabled, pylint would attempt to guess common misconfiguration and emit
+# user-friendly hints instead of false-positive error messages.
+suggestion-mode=yes
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+unsafe-load-any-extension=no
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
+confidence=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once). You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use "--disable=all --enable=classes
+# --disable=W".
+disable=print-statement,
+        parameter-unpacking,
+        unpacking-in-except,
+        old-raise-syntax,
+        backtick,
+        long-suffix,
+        old-ne-operator,
+        old-octal-literal,
+        import-star-module-level,
+        non-ascii-bytes-literal,
+        raw-checker-failed,
+        bad-inline-option,
+        locally-disabled,
+        file-ignored,
+        suppressed-message,
+        useless-suppression,
+        deprecated-pragma,
+        use-symbolic-message-instead,
+        apply-builtin,
+        basestring-builtin,
+        buffer-builtin,
+        cmp-builtin,
+        coerce-builtin,
+        execfile-builtin,
+        file-builtin,
+        long-builtin,
+        raw_input-builtin,
+        reduce-builtin,
+        standarderror-builtin,
+        unicode-builtin,
+        xrange-builtin,
+        coerce-method,
+        delslice-method,
+        getslice-method,
+        setslice-method,
+        no-absolute-import,
+        old-division,
+        dict-iter-method,
+        dict-view-method,
+        next-method-called,
+        metaclass-assignment,
+        indexing-exception,
+        raising-string,
+        reload-builtin,
+        oct-method,
+        hex-method,
+        nonzero-method,
+        cmp-method,
+        input-builtin,
+        round-builtin,
+        intern-builtin,
+        unichr-builtin,
+        map-builtin-not-iterating,
+        zip-builtin-not-iterating,
+        range-builtin-not-iterating,
+        filter-builtin-not-iterating,
+        using-cmp-argument,
+        eq-without-hash,
+        div-method,
+        idiv-method,
+        rdiv-method,
+        exception-message-attribute,
+        invalid-str-codec,
+        sys-max-int,
+        bad-python3-import,
+        deprecated-string-function,
+        deprecated-str-translate-call,
+        deprecated-itertools-function,
+        deprecated-types-field,
+        next-method-defined,
+        dict-items-not-iterating,
+        dict-keys-not-iterating,
+        dict-values-not-iterating,
+        deprecated-operator-function,
+        deprecated-urllib-function,
+        xreadlines-attribute,
+        deprecated-sys-function,
+        exception-escape,
+        comprehension-escape
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once). See also the "--disable" option for examples.
+enable=c-extension-no-member
+
+
+[REPORTS]
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details.
+#msg-template=
+
+# Set the output format. Available formats are text, parseable, colorized, json
+# and msvs (visual studio). You can also give a reporter class, e.g.
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Tells whether to display a full report or only the messages.
+reports=no
+
+# Activate the evaluation score.
+score=yes
+
+
+[REFACTORING]
+
+# Maximum number of nested blocks for function / method body
+max-nested-blocks=5
+
+# Complete name of functions that never returns. When checking for
+# inconsistent-return-statements if a never returning function is called then
+# it will be considered as an explicit return statement and no message will be
+# printed.
+never-returning-functions=sys.exit
+
+
+[LOGGING]
+
+# Format style used to check logging format string. `old` means using %
+# formatting, while `new` is for `{}` formatting.
+logging-format-style=old
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format.
+logging-modules=logging
+
+
+[SPELLING]
+
+# Limits count of emitted suggestions for spelling mistakes.
+max-spelling-suggestions=4
+
+# Spelling dictionary name. Available dictionaries: none. To make it working
+# install python-enchant package..
+spelling-dict=
+
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
+
+# A path to a file that contains private dictionary; one word per line.
+spelling-private-dict-file=
+
+# Tells whether to store unknown words to indicated private dictionary in
+# --spelling-private-dict-file option instead of raising a message.
+spelling-store-unknown-words=no
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,
+      XXX,
+      TODO
+
+
+[TYPECHECK]
+
+# List of decorators that produce context managers, such as
+# contextlib.contextmanager. Add to this list to register other decorators that
+# produce valid context managers.
+contextmanager-decorators=contextlib.contextmanager
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# Tells whether to warn about missing members when the owner of the attribute
+# is inferred to be None.
+ignore-none=yes
+
+# This flag controls whether pylint should warn about no-member and similar
+# checks whenever an opaque object is returned when inferring. The inference
+# can return multiple potential results while evaluating a Python object, but
+# some branches might not be evaluated, which results in partial inference. In
+# that case, it might be useful to still emit no-member and other checks for
+# the rest of the inferred objects.
+ignore-on-opaque-inference=yes
+
+# List of class names for which member attributes should not be checked (useful
+# for classes with dynamically set attributes). This supports the use of
+# qualified names.
+ignored-classes=optparse.Values,thread._local,_thread._local
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis. It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=json
+
+# Show a hint with possible names when a member name was not found. The aspect
+# of finding the hint is based on edit distance.
+missing-member-hint=yes
+
+# The minimum edit distance a name should have in order to be considered a
+# similar match for a missing member name.
+missing-member-hint-distance=1
+
+# The total number of similar names that should be taken in consideration when
+# showing a hint for a missing member.
+missing-member-max-choices=1
+
+
+[VARIABLES]
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid defining new builtins when possible.
+additional-builtins=
+
+# Tells whether unused global variables should be treated as a violation.
+allow-global-unused-variables=yes
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,
+          _cb
+
+# A regular expression matching the name of dummy variables (i.e. expected to
+# not be used).
+dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore.
+ignored-argument-names=_.*|^ignored_|^unused_
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# List of qualified module names which can have objects that can redefine
+# builtins.
+redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
+
+
+[FORMAT]
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+
+# Number of spaces of indent required inside a hanging or continued line.
+indent-after-paren=4
+
+# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
+# tab).
+indent-string='    '
+
+# Maximum number of characters on a single line.
+max-line-length=120
+
+# Maximum number of lines in a module.
+max-module-lines=1000
+
+# List of optional constructs for which whitespace checking is disabled. `dict-
+# separator` is used to allow tabulation in dicts, etc.: {1  : 1,\n222: 2}.
+# `trailing-comma` allows a space between comma and closing bracket: (a, ).
+# `empty-line` allows space-only lines.
+no-space-check=trailing-comma,
+               dict-separator
+
+# Allow the body of a class to be on the same line as the declaration if body
+# contains single statement.
+single-line-class-stmt=no
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+
+[SIMILARITIES]
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=no
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+
+[BASIC]
+
+# Naming style matching correct argument names.
+argument-naming-style=snake_case
+
+# Regular expression matching correct argument names. Overrides argument-
+# naming-style.
+#argument-rgx=
+
+# Naming style matching correct attribute names.
+attr-naming-style=snake_case
+
+# Regular expression matching correct attribute names. Overrides attr-naming-
+# style.
+#attr-rgx=
+
+# Bad variable names which should always be refused, separated by a comma.
+bad-names=foo,
+          bar,
+          baz,
+          toto,
+          tutu,
+          tata
+
+# Naming style matching correct class attribute names.
+class-attribute-naming-style=any
+
+# Regular expression matching correct class attribute names. Overrides class-
+# attribute-naming-style.
+#class-attribute-rgx=
+
+# Naming style matching correct class names.
+class-naming-style=PascalCase
+
+# Regular expression matching correct class names. Overrides class-naming-
+# style.
+#class-rgx=
+
+# Naming style matching correct constant names.
+const-naming-style=UPPER_CASE
+
+# Regular expression matching correct constant names. Overrides const-naming-
+# style.
+#const-rgx=
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
+
+# Naming style matching correct function names.
+function-naming-style=snake_case
+
+# Regular expression matching correct function names. Overrides function-
+# naming-style.
+#function-rgx=
+
+# Good variable names which should always be accepted, separated by a comma.
+good-names=i,
+           j,
+           k,
+           ex,
+           Run,
+           _
+
+# Include a hint for the correct naming format with invalid-name.
+include-naming-hint=no
+
+# Naming style matching correct inline iteration names.
+inlinevar-naming-style=any
+
+# Regular expression matching correct inline iteration names. Overrides
+# inlinevar-naming-style.
+#inlinevar-rgx=
+
+# Naming style matching correct method names.
+method-naming-style=snake_case
+
+# Regular expression matching correct method names. Overrides method-naming-
+# style.
+#method-rgx=
+
+# Naming style matching correct module names.
+module-naming-style=snake_case
+
+# Regular expression matching correct module names. Overrides module-naming-
+# style.
+#module-rgx=
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=^_
+
+# List of decorators that produce properties, such as abc.abstractproperty. Add
+# to this list to register other decorators that produce valid properties.
+# These decorators are taken in consideration only for invalid-name.
+property-classes=abc.abstractproperty
+
+# Naming style matching correct variable names.
+variable-naming-style=snake_case
+
+# Regular expression matching correct variable names. Overrides variable-
+# naming-style.
+#variable-rgx=
+
+
+[STRING]
+
+# This flag controls whether the implicit-str-concat-in-sequence should
+# generate a warning on implicit string concatenation in sequences defined over
+# several lines.
+check-str-concat-over-line-jumps=no
+
+
+[IMPORTS]
+
+# Allow wildcard imports from modules that define __all__.
+allow-wildcard-with-all=no
+
+# Analyse import fallback blocks. This can be used to support both Python 2 and
+# 3 compatible code, which means that the block might have code that exists
+# only in one or another interpreter, leading to false positives when analysed.
+analyse-fallback-blocks=no
+
+# Deprecated modules which should not be used, separated by a comma.
+deprecated-modules=optparse,tkinter.tix
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled).
+ext-import-graph=
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled).
+import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled).
+int-import-graph=
+
+# Force import order to recognize a module as part of the standard
+# compatibility libraries.
+known-standard-library=
+
+# Force import order to recognize a module as part of a third party library.
+known-third-party=enchant
+
+
+[CLASSES]
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,
+                      __new__,
+                      setUp
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,
+                  _fields,
+                  _replace,
+                  _source,
+                  _make
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=cls
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method.
+max-args=5
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Maximum number of boolean expressions in an if statement.
+max-bool-expr=5
+
+# Maximum number of branch for function / method body.
+max-branches=12
+
+# Maximum number of locals for function / method body.
+max-locals=15
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+# Maximum number of return / yield for function / method body.
+max-returns=6
+
+# Maximum number of statements in function / method body.
+max-statements=50
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "BaseException, Exception".
+overgeneral-exceptions=BaseException,
+                       Exception
diff --git a/.travis.yml b/.travis.yml
index 67c4269..3b6764b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,13 +1,14 @@
 language: python
-python:
-  - "3.6"
-  - "3.5"
-  - "3.4"
-  - "2.7"
+
+matrix:
+  include:
+    - python: 3.7
+      dist: xenial
+      sudo: true
 
 # Install dependencies
 install:
- - pip install tornado ptyprocess
+ - pip install tornado ptyprocess python-interface msgpack
 
 # command to run tests
 script: py.test
diff --git a/README.rst b/README.rst
index e2c1a92..bec2637 100644
--- a/README.rst
+++ b/README.rst
@@ -16,11 +16,15 @@ Modules:
   a terminal.
 * ``terminado.uimodule``: Provides a ``Terminal`` Tornado `UI Module
   <http://www.tornadoweb.org/en/stable/guide/templates.html#ui-modules>`_.
+* ``terminado.formats``: Provides message format implementations for JSON, LightPayload (a custom message format) and
+  MessagePack
 
 JS:
 
 * ``terminado/_static/terminado.js``: A lightweight wrapper to set up a
   term.js terminal with a websocket.
+* ``terminado_static/terminad-xtermjs.bundle.js``: An addon for Xterm.js enabling support for terminado supporting all
+  message formats.
 
 Usage example:
 
diff --git a/appveyor.yml b/appveyor.yml
index 11e596c..96ec577 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -4,9 +4,7 @@ skip_branch_with_pr: true
 # environment variables
 environment:
   matrix:
-    - PYTHON: "C:\\Python27-x64"
-    - PYTHON: "C:\\Python35-x64"
-    - PYTHON: "C:\\Python36-x64"
+    - PYTHON: "C:\\Python37-x64"
 
 build: off
 
@@ -24,6 +22,6 @@ install:
   # Install dependencies
   # update path to use installed pip and py.test
   - set PATH=%PYTHON%\\scripts;%PATH%
-  - 'pip install tornado pywinpty pytest'
+  - 'pip install tornado pywinpty pytest python-interface msgpack'
 test_script:
   - 'py.test terminado/tests/basic_test.py::CommonTests::test_basic'
diff --git a/doc/conf.py b/doc/conf.py
index 6f2ae16..81e5c49 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -54,7 +54,7 @@
 # built documents.
 #
 # The short X.Y version.
-version = '0.7'
+version = '0.9'
 # The full version, including alpha/beta/rc tags.
 release = version
 
diff --git a/doc/releasenotes.rst b/doc/releasenotes.rst
index 023d244..1850f49 100644
--- a/doc/releasenotes.rst
+++ b/doc/releasenotes.rst
@@ -1,6 +1,15 @@
 Release notes
 =============
 
+0.9
+---
+
+- Added support for message formats. The following message formats are supported: JSON, LightPayload (a custom
+  message format) and MessagePack. The default message format is JSON, which is fully backwards-compatible. The
+  message format can be switched at runtime.
+- Added Xterm.js addon supporting all the message formats supported on the server-side.
+- Added a command "switch_format" for switching the message format on the fly.
+
 0.7
 ---
 
diff --git a/doc/requirements.txt b/doc/requirements.txt
index 09154a4..02be0a1 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -1,2 +1,4 @@
 ptyprocess
 tornado
+python-interface
+msgpack
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..2695f68
--- /dev/null
+++ b/package.json
@@ -0,0 +1,33 @@
+{
+  "name": "terminado-xtermjs",
+  "version": "1.0.0",
+  "description": "An addon for Xterm.js enabling terminado to be used as backend.",
+  "keywords": [
+    "xtermjs",
+    "terminado",
+    "json",
+    "lightpayload",
+    "messagepack"
+  ],
+  "homepage": "https://github.com/jupyter/terminado",
+  "bugs": "https://github.com/jupyter/terminado/issues",
+  "license": "MIT",
+  "browser": "terminado/_static/terminado-xterm.js",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/jupyter/terminado.git"
+  },
+  "devDependencies": {
+    "browserify": "^16.2.3"
+  },
+  "dependencies": {
+    "messagepack": "^1.1.8"
+  },
+  "files": [
+    "terminado/_static/terminado-xtermjs.js",
+    "terminado/_static/terminado-xtermjs.bundle.js"
+  ],
+  "scripts": {
+    "install": "cd terminado/_static && browserify -r ./terminado-xtermjs -o ./terminado-xtermjs.bundle.js"
+  }
+}
diff --git a/pyproject.toml b/pyproject.toml
index f24d611..3b1b918 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,8 +12,10 @@ requires = [
     "ptyprocess;os_name!='nt'",
     "pywinpty (>=0.5);os_name=='nt'",
     "tornado (>=4)",
+    "python-interface",
+    "msgpack"
 ]
-requires-python=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+requires-python=">=3.7"
 classifiers=[
     "Environment :: Web Environment",
     "License :: OSI Approved :: BSD License",
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..393355d
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,22 @@
+import setuptools
+
+setuptools.setup(
+    name="terminado",
+    version="0.9.2",
+    author="Jupyter Development Team",
+    author_email="jupyter@googlegroups.com",
+    description="A websocket backend for the Xterm.js JavaScript terminal emulator library.",
+    url="https://github.com/jupyter/terminado",
+    packages=setuptools.find_packages(exclude=["doc", "demos", "terminado/_static"]),
+    classifiers=[
+        "Programming Language :: Python :: 3.7"
+    ],
+    license="MIT",
+    install_requires=[
+        "ptyprocess;os_name!='nt'",
+        "pywinpty (>=0.5);os_name=='nt'",
+        "tornado (>=4)",
+        "python-interface",
+        "msgpack"
+    ]
+)
diff --git a/terminado/__init__.py b/terminado/__init__.py
index 643ebfd..48ec12a 100644
--- a/terminado/__init__.py
+++ b/terminado/__init__.py
@@ -12,4 +12,4 @@
 # Prevent a warning about no attached handlers in Python 2
 logging.getLogger(__name__).addHandler(logging.NullHandler())
 
-__version__ = '0.8.2'
+__version__ = '0.9.2'
diff --git a/terminado/_static/terminado-xtermjs.bundle.js b/terminado/_static/terminado-xtermjs.bundle.js
new file mode 100644
index 0000000..9fc6bd7
--- /dev/null
+++ b/terminado/_static/terminado-xtermjs.bundle.js
@@ -0,0 +1,1059 @@
+require=(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
+'use strict';
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+function typeError(tag, expected) {
+    throw new TypeError(`unexpected tag 0x${tag.toString(16)} (${expected} expected)`);
+}
+
+// positive fixint: 0xxx xxxx
+function posFixintTag(i) {
+    return i & 0x7f;
+}
+function isPosFixintTag(tag) {
+    return (tag & 0x80) === 0;
+}
+function readPosFixint(tag) {
+    return tag & 0x7f;
+}
+// negative fixint: 111x xxxx
+function negFixintTag(i) {
+    return 0xe0 | (i & 0x1f);
+}
+function isNegFixintTag(tag) {
+    return (tag & 0xe0) == 0xe0;
+}
+function readNegFixint(tag) {
+    return tag - 0x100;
+}
+// fixstr: 101x xxxx
+function fixstrTag(length) {
+    return 0xa0 | (length & 0x1f);
+}
+function isFixstrTag(tag) {
+    return (tag & 0xe0) == 0xa0;
+}
+function readFixstr(tag) {
+    return tag & 0x1f;
+}
+// fixarray: 1001 xxxx
+function fixarrayTag(length) {
+    return 0x90 | (length & 0x0f);
+}
+function isFixarrayTag(tag) {
+    return (tag & 0xf0) == 0x90;
+}
+function readFixarray(tag) {
+    return tag & 0x0f;
+}
+// fixmap: 1000 xxxx
+function fixmapTag(length) {
+    return 0x80 | (length & 0x0f);
+}
+function isFixmapTag(tag) {
+    return (tag & 0xf0) == 0x80;
+}
+function readFixmap(tag) {
+    return tag & 0x0f;
+}
+
+function createWriteBuffer() {
+    let view = new DataView(new ArrayBuffer(64));
+    let n = 0;
+    function need(x) {
+        if (n + x > view.byteLength) {
+            const arr = new Uint8Array(Math.max(n + x, view.byteLength + 64));
+            arr.set(new Uint8Array(view.buffer.slice(0, n)));
+            view = new DataView(arr.buffer);
+        }
+    }
+    return {
+        put(v) {
+            need(v.byteLength);
+            (new Uint8Array(view.buffer)).set(new Uint8Array(v), n);
+            n += v.byteLength;
+        },
+        putI8(v) {
+            need(1);
+            view.setInt8(n, v);
+            ++n;
+        },
+        putI16(v) {
+            need(2);
+            view.setInt16(n, v);
+            n += 2;
+        },
+        putI32(v) {
+            need(4);
+            view.setInt32(n, v);
+            n += 4;
+        },
+        putI64(v) {
+            need(8);
+            const neg = v < 0;
+            if (neg) {
+                v = -v;
+            }
+            let hi = (v / 0x100000000) | 0;
+            let lo = (v % 0x100000000) | 0;
+            if (neg) {
+                // 2s complement
+                lo = (~lo + 1) | 0;
+                hi = lo === 0 ? (~hi + 1) | 0 : ~hi;
+            }
+            view.setUint32(n, hi);
+            view.setUint32(n + 4, lo);
+            n += 8;
+        },
+        putUi8(v) {
+            need(1);
+            view.setUint8(n, v);
+            ++n;
+        },
+        putUi16(v) {
+            need(2);
+            view.setUint16(n, v);
+            n += 2;
+        },
+        putUi32(v) {
+            need(4);
+            view.setUint32(n, v);
+            n += 4;
+        },
+        putUi64(v) {
+            need(8);
+            view.setUint32(n, (v / 0x100000000) | 0);
+            view.setUint32(n + 4, v % 0x100000000);
+            n += 8;
+        },
+        putF(v) {
+            need(8);
+            view.setFloat64(n, v);
+            n += 8;
+        },
+        ui8array() {
+            return new Uint8Array(view.buffer.slice(0, n));
+        },
+    };
+}
+function createReadBuffer(buf) {
+    let view = new DataView(ArrayBuffer.isView(buf) ? buf.buffer : buf);
+    let n = 0;
+    return {
+        peek() {
+            return view.getUint8(n);
+        },
+        get(len) {
+            n += len;
+            return view.buffer.slice(n - len, n);
+        },
+        getI8() {
+            return view.getInt8(n++);
+        },
+        getI16() {
+            n += 2;
+            return view.getInt16(n - 2);
+        },
+        getI32() {
+            n += 4;
+            return view.getInt32(n - 4);
+        },
+        getI64() {
+            n += 8;
+            const hi = view.getInt32(n - 8);
+            const lo = view.getUint32(n - 4);
+            return hi * 0x100000000 + lo;
+        },
+        getUi8() {
+            return view.getUint8(n++);
+        },
+        getUi16() {
+            n += 2;
+            return view.getUint16(n - 2);
+        },
+        getUi32() {
+            n += 4;
+            return view.getUint32(n - 4);
+        },
+        getUi64() {
+            n += 8;
+            const hi = view.getUint32(n - 8);
+            const lo = view.getUint32(n - 4);
+            return hi * 0x100000000 + lo;
+        },
+        getF32() {
+            n += 4;
+            return view.getFloat32(n - 4);
+        },
+        getF64() {
+            n += 8;
+            return view.getFloat64(n - 8);
+        },
+    };
+}
+function putBlob(buf, blob, baseTag) {
+    const n = blob.byteLength;
+    if (n <= 255) {
+        buf.putUi8(baseTag);
+        buf.putUi8(n);
+    }
+    else if (n <= 65535) {
+        buf.putUi8(baseTag + 1);
+        buf.putUi16(n);
+    }
+    else if (n <= 4294967295) {
+        buf.putUi8(baseTag + 2);
+        buf.putUi32(n);
+    }
+    else {
+        throw new RangeError("length limit exceeded");
+    }
+    buf.put(blob);
+}
+function getBlob(buf) {
+    const tag = buf.getUi8();
+    let n;
+    switch (tag) {
+        case 192 /* Nil */:
+            n = 0;
+            break;
+        case 196 /* Bin8 */:
+        case 217 /* Str8 */:
+            n = buf.getUi8();
+            break;
+        case 197 /* Bin16 */:
+        case 218 /* Str16 */:
+            n = buf.getUi16();
+            break;
+        case 198 /* Bin32 */:
+        case 219 /* Str32 */:
+            n = buf.getUi32();
+            break;
+        default:
+            if (!isFixstrTag(tag)) {
+                typeError(tag, "bytes or string");
+            }
+            n = readFixstr(tag);
+    }
+    return buf.get(n);
+}
+function putArrHeader(buf, n) {
+    if (n < 16) {
+        buf.putUi8(fixarrayTag(n));
+    }
+    else {
+        putCollectionHeader(buf, 220 /* Array16 */, n);
+    }
+}
+function getArrHeader(buf, expect) {
+    const tag = buf.getUi8();
+    const n = isFixarrayTag(tag)
+        ? readFixarray(tag)
+        : getCollectionHeader(buf, tag, 220 /* Array16 */, "array");
+    if (expect != null && n !== expect) {
+        throw new Error(`invalid array header size ${n}`);
+    }
+    return n;
+}
+function putMapHeader(buf, n) {
+    if (n < 16) {
+        buf.putUi8(fixmapTag(n));
+    }
+    else {
+        putCollectionHeader(buf, 222 /* Map16 */, n);
+    }
+}
+function getMapHeader(buf, expect) {
+    const tag = buf.getUi8();
+    const n = isFixmapTag(tag)
+        ? readFixmap(tag)
+        : getCollectionHeader(buf, tag, 222 /* Map16 */, "map");
+    if (expect != null && n !== expect) {
+        throw new Error(`invalid map header size ${n}`);
+    }
+    return n;
+}
+function putCollectionHeader(buf, baseTag, n) {
+    if (n <= 65535) {
+        buf.putUi8(baseTag);
+        buf.putUi16(n);
+    }
+    else if (n <= 4294967295) {
+        buf.putUi8(baseTag + 1);
+        buf.putUi32(n);
+    }
+    else {
+        throw new RangeError("length limit exceeded");
+    }
+}
+function getCollectionHeader(buf, tag, baseTag, typename) {
+    switch (tag) {
+        case 192 /* Nil */:
+            return 0;
+        case baseTag: // 16 bit
+            return buf.getUi16();
+        case baseTag + 1: // 32 bit
+            return buf.getUi32();
+        default:
+            typeError(tag, typename);
+    }
+}
+
+const Any = {
+    enc(buf, v) {
+        typeOf(v).enc(buf, v);
+    },
+    dec(buf) {
+        return tagType(buf.peek()).dec(buf);
+    },
+};
+const Nil = {
+    enc(buf, v) {
+        buf.putUi8(192 /* Nil */);
+    },
+    dec(buf) {
+        const tag = buf.getUi8();
+        if (tag !== 192 /* Nil */) {
+            typeError(tag, "nil");
+        }
+        return null;
+    },
+};
+const Bool = {
+    enc(buf, v) {
+        buf.putUi8(v ? 195 /* True */ : 194 /* False */);
+    },
+    dec(buf) {
+        const tag = buf.getUi8();
+        switch (tag) {
+            case 192 /* Nil */:
+            case 194 /* False */:
+                return false;
+            case 195 /* True */:
+                return true;
+            default:
+                typeError(tag, "bool");
+        }
+    },
+};
+const Int = {
+    enc(buf, v) {
+        if (-128 <= v && v <= 127) {
+            if (v >= 0) {
+                buf.putUi8(posFixintTag(v));
+            }
+            else if (v > -32) {
+                buf.putUi8(negFixintTag(v));
+            }
+            else {
+                buf.putUi8(208 /* Int8 */);
+                buf.putUi8(v);
+            }
+        }
+        else if (-32768 <= v && v <= 32767) {
+            buf.putI8(209 /* Int16 */);
+            buf.putI16(v);
+        }
+        else if (-2147483648 <= v && v <= 2147483647) {
+            buf.putI8(210 /* Int32 */);
+            buf.putI32(v);
+        }
+        else {
+            buf.putI8(211 /* Int64 */);
+            buf.putI64(v);
+        }
+    },
+    dec(buf) {
+        const tag = buf.getUi8();
+        if (isPosFixintTag(tag)) {
+            return readPosFixint(tag);
+        }
+        else if (isNegFixintTag(tag)) {
+            return readNegFixint(tag);
+        }
+        switch (tag) {
+            case 192 /* Nil */:
+                return 0;
+            // signed int types
+            case 208 /* Int8 */:
+                return buf.getI8();
+            case 209 /* Int16 */:
+                return buf.getI16();
+            case 210 /* Int32 */:
+                return buf.getI32();
+            case 211 /* Int64 */:
+                return buf.getI64();
+            // unsigned int types
+            case 204 /* Uint8 */:
+                return buf.getUi8();
+            case 205 /* Uint16 */:
+                return buf.getUi16();
+            case 206 /* Uint32 */:
+                return buf.getUi32();
+            case 207 /* Uint64 */:
+                return buf.getUi64();
+            default:
+                typeError(tag, "int");
+        }
+    },
+};
+const Uint = {
+    enc(buf, v) {
+        if (v < 0) {
+            throw new Error(`not an uint: ${v}`);
+        }
+        else if (v <= 127) {
+            buf.putUi8(posFixintTag(v));
+        }
+        else if (v <= 255) {
+            buf.putUi8(204 /* Uint8 */);
+            buf.putUi8(v);
+        }
+        else if (v <= 65535) {
+            buf.putUi8(205 /* Uint16 */);
+            buf.putUi16(v);
+        }
+        else if (v <= 4294967295) {
+            buf.putUi8(206 /* Uint32 */);
+            buf.putUi32(v);
+        }
+        else {
+            buf.putUi8(207 /* Uint64 */);
+            buf.putUi64(v);
+        }
+    },
+    dec(buf) {
+        const v = Int.dec(buf);
+        if (v < 0) {
+            throw new RangeError("uint underflow");
+        }
+        return v;
+    },
+};
+const Float = {
+    enc(buf, v) {
+        buf.putUi8(203 /* Float64 */);
+        buf.putF(v);
+    },
+    dec(buf) {
+        const tag = buf.getUi8();
+        switch (tag) {
+            case 192 /* Nil */:
+                return 0;
+            case 202 /* Float32 */:
+                return buf.getF32();
+            case 203 /* Float64 */:
+                return buf.getF64();
+            default:
+                typeError(tag, "float");
+        }
+    },
+};
+const Bytes = {
+    enc(buf, v) {
+        putBlob(buf, v, 196 /* Bin8 */);
+    },
+    dec: getBlob,
+};
+const Str = {
+    enc(buf, v) {
+        const utf8 = toUTF8(v);
+        if (utf8.byteLength < 32) {
+            buf.putUi8(fixstrTag(utf8.byteLength));
+            buf.put(utf8);
+        }
+        else {
+            putBlob(buf, utf8, 217 /* Str8 */);
+        }
+    },
+    dec(buf) {
+        return fromUTF8(getBlob(buf));
+    },
+};
+const Time = {
+    enc(buf, v) {
+        const ms = v.getTime();
+        buf.putUi8(199 /* Ext8 */);
+        buf.putUi8(12);
+        buf.putI8(-1);
+        buf.putUi32((ms % 1000) * 1000000);
+        buf.putI64(ms / 1000);
+    },
+    dec(buf) {
+        const tag = buf.getUi8();
+        switch (tag) {
+            case 214 /* FixExt4 */: // 32-bit seconds
+                if (buf.getI8() === -1) {
+                    return new Date(buf.getUi32() * 1000);
+                }
+                break;
+            case 215 /* FixExt8 */: // 34-bit seconds + 30-bit nanoseconds
+                if (buf.getI8() === -1) {
+                    const lo = buf.getUi32();
+                    const hi = buf.getUi32();
+                    // seconds: hi + (lo&0x3)*0x100000000
+                    // nanoseconds: lo>>2 == lo/4
+                    return new Date((hi + (lo & 0x3) * 0x100000000) * 1000 + lo / 4000000);
+                }
+                break;
+            case 199 /* Ext8 */: // 64-bit seconds + 32-bit nanoseconds
+                if (buf.getUi8() === 12 && buf.getI8() === -1) {
+                    const ns = buf.getUi32();
+                    const s = buf.getI64();
+                    return new Date(s * 1000 + ns / 1000000);
+                }
+                break;
+        }
+        typeError(tag, "time");
+    },
+};
+const Arr = TypedArr(Any);
+const Map = TypedMap(Any, Any);
+function TypedArr(valueT) {
+    return {
+        encHeader: putArrHeader,
+        decHeader: getArrHeader,
+        enc(buf, v) {
+            putArrHeader(buf, v.length);
+            v.forEach(x => valueT.enc(buf, x));
+        },
+        dec(buf) {
+            const res = [];
+            for (let n = getArrHeader(buf); n > 0; --n) {
+                res.push(valueT.dec(buf));
+            }
+            return res;
+        },
+    };
+}
+function TypedMap(keyT, valueT) {
+    return {
+        encHeader: putMapHeader,
+        decHeader: getMapHeader,
+        enc(buf, v) {
+            const props = Object.keys(v);
+            putMapHeader(buf, props.length);
+            props.forEach(p => {
+                keyT.enc(buf, p);
+                valueT.enc(buf, v[p]);
+            });
+        },
+        dec(buf) {
+            const res = {};
+            for (let n = getMapHeader(buf); n > 0; --n) {
+                const k = keyT.dec(buf);
+                res[k] = valueT.dec(buf);
+            }
+            return res;
+        },
+    };
+}
+function structEncoder(fields) {
+    const ordinals = Object.keys(fields);
+    return (buf, v) => {
+        putMapHeader(buf, ordinals.length);
+        ordinals.forEach(ord => {
+            const f = fields[ord];
+            Int.enc(buf, Number(ord));
+            f[1].enc(buf, v[f[0]]);
+        });
+    };
+}
+function structDecoder(fields) {
+    return (buf) => {
+        const res = {};
+        for (let n = getMapHeader(buf); n > 0; --n) {
+            const f = fields[Int.dec(buf)];
+            if (f) {
+                res[f[0]] = f[1].dec(buf);
+            }
+            else {
+                Any.dec(buf);
+            }
+        }
+        return res;
+    };
+}
+function Struct(fields) {
+    return {
+        enc: structEncoder(fields),
+        dec: structDecoder(fields),
+    };
+}
+function unionEncoder(branches) {
+    return (buf, v) => {
+        putArrHeader(buf, 2);
+        const ord = branches.ordinalOf(v);
+        Int.enc(buf, ord);
+        branches[ord].enc(buf, v);
+    };
+}
+function unionDecoder(branches) {
+    return (buf) => {
+        getArrHeader(buf, 2);
+        const t = branches[Int.dec(buf)];
+        if (!t) {
+            throw new TypeError("invalid union type");
+        }
+        return t.dec(buf);
+    };
+}
+function Union(branches) {
+    return {
+        enc: unionEncoder(branches),
+        dec: unionDecoder(branches),
+    };
+}
+function toUTF8(v) {
+    const n = v.length;
+    const bin = new Uint8Array(4 * n);
+    let pos = 0, i = 0, c;
+    while (i < n) {
+        c = v.charCodeAt(i++);
+        if ((c & 0xfc00) === 0xd800) {
+            c = (c << 10) + v.charCodeAt(i++) - 0x35fdc00;
+        }
+        if (c < 0x80) {
+            bin[pos++] = c;
+        }
+        else if (c < 0x800) {
+            bin[pos++] = 0xc0 + (c >> 6);
+            bin[pos++] = 0x80 + (c & 0x3f);
+        }
+        else if (c < 0x10000) {
+            bin[pos++] = 0xe0 + (c >> 12);
+            bin[pos++] = 0x80 + ((c >> 6) & 0x3f);
+            bin[pos++] = 0x80 + (c & 0x3f);
+        }
+        else {
+            bin[pos++] = 0xf0 + (c >> 18);
+            bin[pos++] = 0x80 + ((c >> 12) & 0x3f);
+            bin[pos++] = 0x80 + ((c >> 6) & 0x3f);
+            bin[pos++] = 0x80 + (c & 0x3f);
+        }
+    }
+    return bin.buffer.slice(0, pos);
+}
+function fromUTF8(buf) {
+    const bin = new Uint8Array(buf);
+    let n, c, codepoints = [];
+    for (let i = 0; i < bin.length;) {
+        c = bin[i++];
+        n = 0;
+        switch (c & 0xf0) {
+            case 0xf0:
+                n = 3;
+                break;
+            case 0xe0:
+                n = 2;
+                break;
+            case 0xd0:
+            case 0xc0:
+                n = 1;
+                break;
+        }
+        if (n !== 0) {
+            c &= (1 << (6 - n)) - 1;
+            for (let k = 0; k < n; ++k) {
+                c = (c << 6) + (bin[i++] & 0x3f);
+            }
+        }
+        codepoints.push(c);
+    }
+    return String.fromCodePoint.apply(null, codepoints);
+}
+function typeOf(v) {
+    switch (typeof v) {
+        case "undefined":
+            return Nil;
+        case "boolean":
+            return Bool;
+        case "number":
+            return !isFinite(v) || Math.floor(v) !== v ? Float
+                : v < 0 ? Int
+                    : Uint;
+        case "string":
+            return Str;
+        case "object":
+            return v === null ? Nil
+                : Array.isArray(v) ? Arr
+                    : v instanceof Uint8Array || v instanceof ArrayBuffer ? Bytes
+                        : v instanceof Date ? Time
+                            : Map;
+        default:
+            throw new TypeError(`unsupported type ${typeof v}`);
+    }
+}
+function tagType(tag) {
+    switch (tag) {
+        case 192 /* Nil */:
+            return Nil;
+        case 194 /* False */:
+        case 195 /* True */:
+            return Bool;
+        case 208 /* Int8 */:
+        case 209 /* Int16 */:
+        case 210 /* Int32 */:
+        case 211 /* Int64 */:
+            return Int;
+        case 204 /* Uint8 */:
+        case 205 /* Uint16 */:
+        case 206 /* Uint32 */:
+        case 207 /* Uint64 */:
+            return Uint;
+        case 202 /* Float32 */:
+        case 203 /* Float64 */:
+            return Float;
+        case 196 /* Bin8 */:
+        case 197 /* Bin16 */:
+        case 198 /* Bin32 */:
+            return Bytes;
+        case 217 /* Str8 */:
+        case 218 /* Str16 */:
+        case 219 /* Str32 */:
+            return Str;
+        case 220 /* Array16 */:
+        case 221 /* Array32 */:
+            return Arr;
+        case 222 /* Map16 */:
+        case 223 /* Map32 */:
+            return Map;
+        case 214 /* FixExt4 */:
+        case 215 /* FixExt8 */:
+        case 199 /* Ext8 */:
+            return Time;
+        default:
+            if (isPosFixintTag(tag) || isNegFixintTag(tag)) {
+                return Int;
+            }
+            if (isFixstrTag(tag)) {
+                return Str;
+            }
+            if (isFixarrayTag(tag)) {
+                return Arr;
+            }
+            if (isFixmapTag(tag)) {
+                return Map;
+            }
+            throw new TypeError(`unsupported tag ${tag}`);
+    }
+}
+
+function encode(v, typ) {
+    const buf = createWriteBuffer();
+    (typ || Any).enc(buf, v);
+    return buf.ui8array();
+}
+function decode(buf, typ) {
+    return (typ || Any).dec(createReadBuffer(buf));
+}
+
+exports.Nil = Nil;
+exports.Bool = Bool;
+exports.Int = Int;
+exports.Uint = Uint;
+exports.Float = Float;
+exports.Bytes = Bytes;
+exports.Str = Str;
+exports.TypedArr = TypedArr;
+exports.TypedMap = TypedMap;
+exports.Time = Time;
+exports.Any = Any;
+exports.Arr = Arr;
+exports.Map = Map;
+exports.Struct = Struct;
+exports.Union = Union;
+exports.structEncoder = structEncoder;
+exports.structDecoder = structDecoder;
+exports.unionEncoder = unionEncoder;
+exports.unionDecoder = unionDecoder;
+exports.encode = encode;
+exports.decode = decode;
+
+
+},{}],"/terminado-xtermjs":[function(require,module,exports){
+/**
+ * Swaps keys and values in the given object.
+ * Non-string values will be converted to string in order to be used as key.
+ *
+ * @param {Object} object
+ *  The keys and values to swap.
+ * @return {Object}
+ *  A new object with keys and values swapped.
+ */
+function swap(object){
+  // the new object
+  var swappedObject = {};
+  // loop all keys
+  for (var key in object) {
+    // get the value to be used as key, converting it to string, if needed
+    var value = typeof object[key] == "string" ? object[key] : object[key].toString();
+    // add the swapped key/value pair
+    swappedObject[value] = key;
+  }
+  // return the new object
+  return swappedObject;
+}
+
+// define the message formats
+var formats = {
+  JSON: {
+    /**
+     * Packs the given type and data as JSON-serialised string.
+     *
+     * @param {String} type
+     *  A tornado message type.
+     * @param {String|Array} message
+     *  The message to pack.
+     * @return {String}
+     *  The JSON-serialised pack.
+     */
+    pack: function pack(type, message) {
+      // init the pack with the type
+      var pack = [type];
+
+      // check if the message is an array
+      if (message instanceof Array) {
+        // add the message's elements to the pack
+        pack = pack.concat(message);
+      } else {
+        // add the message to the pack
+        pack.push(message);
+      }
+
+      // return the JSON-stringyfied pack
+      return JSON.stringify(pack);
+    },
+
+    /**
+     * Unpacks the given JSON-serialised string.
+     *
+     * @param {String} data
+     *  A JSON-serialised string.
+     * @return {Array}
+     *  A type and the message (parts).
+     */
+    unpack: function unpack(data) {
+      // return the unpacked type and message (parts)
+      return JSON.parse(data);
+    }
+  },
+
+  LightPayload: {
+    // forward map mapping terminado types to LightPayload types
+    TYPES: {
+      stdin: "I",
+      stdout: "O",
+      set_size: "S",
+      setup: "C",
+      disconnect: "D",
+      switch_format: "F"
+    },
+
+    /**
+     * Packs the given type and data as LightPayload-serialised string.
+     *
+     * @param {String} type
+     *  A tornado message type.
+     * @param {String|Array} message
+     *  The message to pack.
+     * @return {String}
+     *  The LightPayload-serialised pack
+     */
+    pack: function pack(type, message) {
+      // return the LightPayload-serialised string
+      return this.TYPES[type] + "|" + (message instanceof Array ? message.join(",") : message);
+    },
+
+    /**
+     * Unpacks the given LightPayload-serialised string.
+     *
+     * @param {String} data
+     *  A LightPayload-serialised string.
+     * @return {Array}
+     *  A type and the message (parts).
+     */
+    unpack: function unpack(data) {
+      // return the unpacked type and message
+      return [this.RTYPES[data[0]], data.substring(2)];
+    }
+  },
+
+  // forward map mapping terminado types to MessagePack types
+  MessagePack: {
+    TYPES: {
+      stdin: 1,
+      stdout: 2,
+      set_size: 3,
+      setup: 4,
+      disconnect: 5,
+      switch_format: 6
+    },
+
+    /**
+     * Packs the given type and data as MessagePack-serialised binary data.
+     *
+     * @param {String} type
+     *  A tornado message type.
+     * @param {String|Array} message
+     *  The message to pack.
+     * @return {ByteArray}
+     *  The MessagePack-serialised pack.
+     */
+    pack: function pack(type, message) {
+      // init the pack with the type mapped to the corresponding MessagePack type
+      var pack = [this.TYPES[type]];
+
+      // check if the message is an array
+      if (message instanceof Array) {
+        // add the message's elements to the pack
+        pack = pack.concat(message);
+      } else {
+        // add the message to the pack
+        pack.push(message);
+      }
+
+      // return the MessagePack-serialised pack
+      return require("messagepack").encode(pack);
+    },
+
+    /**
+     * Unpacks the given MessagePack-serialised binary data.
+     *
+     * @param {Blob} data
+     *  A LightPayload-serialised string.
+     * @return {Array}
+     *  A type and the message (parts).
+     */
+    unpack: function unpack(data) {
+      // a blob can only be read async, return a promise
+      return new Promise(function(resolve, reject) {
+        // create a file reader
+        var fileReader = new FileReader();
+        // when the blob is read
+        fileReader.onload = function(event) {
+          // unpack the MessagePack-serialised binary data
+          var message = require("messagepack").decode(event.target.result);
+          // map the MessagePack type to the corresponding terminado type
+          message[0] = swap(formats.MessagePack.TYPES)[message[0].toString()];
+          // resolve the promise
+          resolve(message);
+        };
+        // on error reject the promise
+        fileReader.onerror = reject;
+        // on abort reject the promise
+        fileReader.onabort = reject;
+        // read the blob
+        fileReader.readAsArrayBuffer(data);
+      });
+    }
+  }
+};
+
+// reverse map mapping MessagePack types to terminado types
+formats.LightPayload.RTYPES = swap(formats.LightPayload.TYPES);
+// reverse map mapping LightPayload types to terminado types
+formats.MessagePack.RTYPES =  swap(formats.MessagePack.TYPES);
+
+// define the terminado addon
+var terminado = {
+  // define the default message format
+  DEFAULT_MESSAGE_FORMAT: "JSON",
+
+  apply: function apply(terminalConstructor, messageFormat, switchMessageFormat) {
+    // default to the default message format, if no message format is given
+    messageFormat = messageFormat || this.DEFAULT_MESSAGE_FORMAT;
+    // default to switching message format, if not given and message format is not the default message format
+    switchMessageFormat = switchMessageFormat !== undefined ? switchMessageFormat : 
+        (messageFormat != this.DEFAULT_MESSAGE_FORMAT ? true : false);
+
+    // closure cache the message format and if to switch the message format
+    terminalConstructor.prototype.terminadoAttach = (function(messageFormat) {
+      return function (socket, bidirectional, buffered) {
+        return terminado.terminadoAttach(this, socket, bidirectional, buffered, messageFormat, switchMessageFormat);
+      };
+    })(messageFormat, switchMessageFormat);
+
+    terminalConstructor.prototype.terminadoDetach = function (socket) {
+      return terminado.terminadoDetach(this, socket);
+    };
+  },
+
+  terminadoAttach: function terminadoAttach(term, socket, bidirectional, buffered, messageFormat, switchMessageFormat) {
+    // check if to switch the message format
+    if (switchMessageFormat) {
+      // tell terminado which message format to use from now on
+      socket.send(formats[this.DEFAULT_MESSAGE_FORMAT].pack("switch_format", messageFormat));
+    }
+
+    var addonTerminal = term;
+    bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
+    addonTerminal.__socket = socket;
+    addonTerminal.__flushBuffer = function () {
+      addonTerminal.write(addonTerminal.__attachSocketBuffer);
+      addonTerminal.__attachSocketBuffer = null;
+    };
+    addonTerminal.__pushToBuffer = function (data) {
+      if (addonTerminal.__attachSocketBuffer) {
+        addonTerminal.__attachSocketBuffer += data;
+      }
+      else {
+        addonTerminal.__attachSocketBuffer = data;
+        setTimeout(addonTerminal.__flushBuffer, 10);
+      }
+    };
+    addonTerminal.__getMessage = function (ev) {
+      function processMessage(message) {
+        if (message[0] === 'stdout') {
+          if (buffered) {
+            addonTerminal.__pushToBuffer(message[1]);
+          }
+          else {
+            addonTerminal.write(message[1]);
+          }
+        }
+      }
+
+      // unpack the data
+      var data = formats[messageFormat].unpack(ev.data);
+      // check if data is still unpacking
+      if (data instanceof Promise) {
+        // wait for the data to be unpacked and process it once unpacked
+        data.then(processMessage);
+      } else {
+        // process the data
+        processMessage(data);
+      }          
+    };
+    addonTerminal.__sendData = function (data) {
+      // pack and send the data
+      socket.send(formats[messageFormat].pack("stdin", data));
+    };
+    addonTerminal.__setSize = function (size) {
+      // pack and set the "set_size" data
+      socket.send(formats[messageFormat].pack("set_size", [size.rows, size.cols]));
+    };
+    socket.addEventListener('message', addonTerminal.__getMessage);
+    if (bidirectional) {
+      addonTerminal.on('data', addonTerminal.__sendData);
+    }
+    addonTerminal.on('resize', addonTerminal.__setSize);
+    socket.addEventListener('close', function () { return terminado.terminadoDetach(addonTerminal, socket); });
+    socket.addEventListener('error', function () { return terminado.terminadoDetach(addonTerminal, socket); });
+  },
+
+  terminadoDetach: function terminadoDetach(term, socket) {
+    var addonTerminal = term;
+    addonTerminal.off('data', addonTerminal.__sendData);
+    socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
+    if (socket) {
+      socket.removeEventListener('message', addonTerminal.__getMessage);
+    }
+    delete addonTerminal.__socket;
+  }
+};
+
+// export the terminando addon
+module.exports = terminado;
+},{"messagepack":1}]},{},[]);
diff --git a/terminado/_static/terminado-xtermjs.js b/terminado/_static/terminado-xtermjs.js
new file mode 100644
index 0000000..8457ea1
--- /dev/null
+++ b/terminado/_static/terminado-xtermjs.js
@@ -0,0 +1,283 @@
+/**
+ * Swaps keys and values in the given object.
+ * Non-string values will be converted to string in order to be used as key.
+ *
+ * @param {Object} object
+ *  The keys and values to swap.
+ * @return {Object}
+ *  A new object with keys and values swapped.
+ */
+function swap(object){
+  // the new object
+  var swappedObject = {};
+  // loop all keys
+  for (var key in object) {
+    // get the value to be used as key, converting it to string, if needed
+    var value = typeof object[key] == "string" ? object[key] : object[key].toString();
+    // add the swapped key/value pair
+    swappedObject[value] = key;
+  }
+  // return the new object
+  return swappedObject;
+}
+
+// define the message formats
+var formats = {
+  JSON: {
+    /**
+     * Packs the given type and data as JSON-serialised string.
+     *
+     * @param {String} type
+     *  A tornado message type.
+     * @param {String|Array} message
+     *  The message to pack.
+     * @return {String}
+     *  The JSON-serialised pack.
+     */
+    pack: function pack(type, message) {
+      // init the pack with the type
+      var pack = [type];
+
+      // check if the message is an array
+      if (message instanceof Array) {
+        // add the message's elements to the pack
+        pack = pack.concat(message);
+      } else {
+        // add the message to the pack
+        pack.push(message);
+      }
+
+      // return the JSON-stringyfied pack
+      return JSON.stringify(pack);
+    },
+
+    /**
+     * Unpacks the given JSON-serialised string.
+     *
+     * @param {String} data
+     *  A JSON-serialised string.
+     * @return {Array}
+     *  A type and the message (parts).
+     */
+    unpack: function unpack(data) {
+      // return the unpacked type and message (parts)
+      return JSON.parse(data);
+    }
+  },
+
+  LightPayload: {
+    // forward map mapping terminado types to LightPayload types
+    TYPES: {
+      stdin: "I",
+      stdout: "O",
+      set_size: "S",
+      setup: "C",
+      disconnect: "D",
+      switch_format: "F"
+    },
+
+    /**
+     * Packs the given type and data as LightPayload-serialised string.
+     *
+     * @param {String} type
+     *  A tornado message type.
+     * @param {String|Array} message
+     *  The message to pack.
+     * @return {String}
+     *  The LightPayload-serialised pack
+     */
+    pack: function pack(type, message) {
+      // return the LightPayload-serialised string
+      return this.TYPES[type] + "|" + (message instanceof Array ? message.join(",") : message);
+    },
+
+    /**
+     * Unpacks the given LightPayload-serialised string.
+     *
+     * @param {String} data
+     *  A LightPayload-serialised string.
+     * @return {Array}
+     *  A type and the message (parts).
+     */
+    unpack: function unpack(data) {
+      // return the unpacked type and message
+      return [this.RTYPES[data[0]], data.substring(2)];
+    }
+  },
+
+  // forward map mapping terminado types to MessagePack types
+  MessagePack: {
+    TYPES: {
+      stdin: 1,
+      stdout: 2,
+      set_size: 3,
+      setup: 4,
+      disconnect: 5,
+      switch_format: 6
+    },
+
+    /**
+     * Packs the given type and data as MessagePack-serialised binary data.
+     *
+     * @param {String} type
+     *  A tornado message type.
+     * @param {String|Array} message
+     *  The message to pack.
+     * @return {ByteArray}
+     *  The MessagePack-serialised pack.
+     */
+    pack: function pack(type, message) {
+      // init the pack with the type mapped to the corresponding MessagePack type
+      var pack = [this.TYPES[type]];
+
+      // check if the message is an array
+      if (message instanceof Array) {
+        // add the message's elements to the pack
+        pack = pack.concat(message);
+      } else {
+        // add the message to the pack
+        pack.push(message);
+      }
+
+      // return the MessagePack-serialised pack
+      return require("messagepack").encode(pack);
+    },
+
+    /**
+     * Unpacks the given MessagePack-serialised binary data.
+     *
+     * @param {Blob} data
+     *  A LightPayload-serialised string.
+     * @return {Array}
+     *  A type and the message (parts).
+     */
+    unpack: function unpack(data) {
+      // a blob can only be read async, return a promise
+      return new Promise(function(resolve, reject) {
+        // create a file reader
+        var fileReader = new FileReader();
+        // when the blob is read
+        fileReader.onload = function(event) {
+          // unpack the MessagePack-serialised binary data
+          var message = require("messagepack").decode(event.target.result);
+          // map the MessagePack type to the corresponding terminado type
+          message[0] = swap(formats.MessagePack.TYPES)[message[0].toString()];
+          // resolve the promise
+          resolve(message);
+        };
+        // on error reject the promise
+        fileReader.onerror = reject;
+        // on abort reject the promise
+        fileReader.onabort = reject;
+        // read the blob
+        fileReader.readAsArrayBuffer(data);
+      });
+    }
+  }
+};
+
+// reverse map mapping MessagePack types to terminado types
+formats.LightPayload.RTYPES = swap(formats.LightPayload.TYPES);
+// reverse map mapping LightPayload types to terminado types
+formats.MessagePack.RTYPES =  swap(formats.MessagePack.TYPES);
+
+// define the terminado addon
+var terminado = {
+  // define the default message format
+  DEFAULT_MESSAGE_FORMAT: "JSON",
+
+  apply: function apply(terminalConstructor, messageFormat, switchMessageFormat) {
+    // default to the default message format, if no message format is given
+    messageFormat = messageFormat || this.DEFAULT_MESSAGE_FORMAT;
+    // default to switching message format, if not given and message format is not the default message format
+    switchMessageFormat = switchMessageFormat !== undefined ? switchMessageFormat : 
+        (messageFormat != this.DEFAULT_MESSAGE_FORMAT ? true : false);
+
+    // closure cache the message format and if to switch the message format
+    terminalConstructor.prototype.terminadoAttach = (function(messageFormat) {
+      return function (socket, bidirectional, buffered) {
+        return terminado.terminadoAttach(this, socket, bidirectional, buffered, messageFormat, switchMessageFormat);
+      };
+    })(messageFormat, switchMessageFormat);
+
+    terminalConstructor.prototype.terminadoDetach = function (socket) {
+      return terminado.terminadoDetach(this, socket);
+    };
+  },
+
+  terminadoAttach: function terminadoAttach(term, socket, bidirectional, buffered, messageFormat, switchMessageFormat) {
+    // check if to switch the message format
+    if (switchMessageFormat) {
+      // tell terminado which message format to use from now on
+      socket.send(formats[this.DEFAULT_MESSAGE_FORMAT].pack("switch_format", messageFormat));
+    }
+
+    var addonTerminal = term;
+    bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
+    addonTerminal.__socket = socket;
+    addonTerminal.__flushBuffer = function () {
+      addonTerminal.write(addonTerminal.__attachSocketBuffer);
+      addonTerminal.__attachSocketBuffer = null;
+    };
+    addonTerminal.__pushToBuffer = function (data) {
+      if (addonTerminal.__attachSocketBuffer) {
+        addonTerminal.__attachSocketBuffer += data;
+      }
+      else {
+        addonTerminal.__attachSocketBuffer = data;
+        setTimeout(addonTerminal.__flushBuffer, 10);
+      }
+    };
+    addonTerminal.__getMessage = function (ev) {
+      function processMessage(message) {
+        if (message[0] === 'stdout') {
+          if (buffered) {
+            addonTerminal.__pushToBuffer(message[1]);
+          }
+          else {
+            addonTerminal.write(message[1]);
+          }
+        }
+      }
+
+      // unpack the data
+      var data = formats[messageFormat].unpack(ev.data);
+      // check if data is still unpacking
+      if (data instanceof Promise) {
+        // wait for the data to be unpacked and process it once unpacked
+        data.then(processMessage);
+      } else {
+        // process the data
+        processMessage(data);
+      }          
+    };
+    addonTerminal.__sendData = function (data) {
+      // pack and send the data
+      socket.send(formats[messageFormat].pack("stdin", data));
+    };
+    addonTerminal.__setSize = function (size) {
+      // pack and set the "set_size" data
+      socket.send(formats[messageFormat].pack("set_size", [size.rows, size.cols]));
+    };
+    socket.addEventListener('message', addonTerminal.__getMessage);
+    if (bidirectional) {
+      addonTerminal.on('data', addonTerminal.__sendData);
+    }
+    addonTerminal.on('resize', addonTerminal.__setSize);
+    socket.addEventListener('close', function () { return terminado.terminadoDetach(addonTerminal, socket); });
+    socket.addEventListener('error', function () { return terminado.terminadoDetach(addonTerminal, socket); });
+  },
+
+  terminadoDetach: function terminadoDetach(term, socket) {
+    var addonTerminal = term;
+    addonTerminal.off('data', addonTerminal.__sendData);
+    socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
+    if (socket) {
+      socket.removeEventListener('message', addonTerminal.__getMessage);
+    }
+    delete addonTerminal.__socket;
+  }
+};
+
+// export the terminando addon
+module.exports = terminado;
\ No newline at end of file
diff --git a/terminado/formats/__init__.py b/terminado/formats/__init__.py
new file mode 100644
index 0000000..1fb6d6e
--- /dev/null
+++ b/terminado/formats/__init__.py
@@ -0,0 +1 @@
+"""Message formats for reading and writing to the WebSocket."""
diff --git a/terminado/formats/format.py b/terminado/formats/format.py
new file mode 100644
index 0000000..17fec5f
--- /dev/null
+++ b/terminado/formats/format.py
@@ -0,0 +1,16 @@
+"""Interface for classes wanting to implement a message format."""
+
+from interface import Interface
+
+class MessageFormat(Interface):
+    """Interface for message formats."""
+
+    @staticmethod
+    def pack(command: str, message):
+        """Pack the given command and message for writing to the socket."""
+        pass
+
+    @staticmethod
+    def unpack(data) -> list:
+        """Unpack the data read from the socket."""
+        pass
diff --git a/terminado/formats/json.py b/terminado/formats/json.py
new file mode 100644
index 0000000..99bec4a
--- /dev/null
+++ b/terminado/formats/json.py
@@ -0,0 +1,29 @@
+"""Message format implementation writing and reading data as JSON.
+   See http://json.org for JSON.
+"""
+
+import json
+from interface import implements
+from .format import MessageFormat
+
+class JSONMessageFormat(implements(MessageFormat)):
+    """Message format implementation writing and reading data as JSON.
+       See http://json.org for JSON.
+    """
+
+    @staticmethod
+    def pack(command: str, message):
+        """Pack the given command and message for writing to the socket."""
+        pack = [command]
+
+        if isinstance(message, list):
+            pack = pack + message
+        else:
+            pack.append(message)
+
+        return json.dumps(pack)
+
+    @staticmethod
+    def unpack(data) -> list:
+        """Unpack the data read from the socket."""
+        return json.loads(data)
diff --git a/terminado/formats/lightpayload.py b/terminado/formats/lightpayload.py
new file mode 100644
index 0000000..dd0a913
--- /dev/null
+++ b/terminado/formats/lightpayload.py
@@ -0,0 +1,60 @@
+"""Message format implementation writing and reading data as custom string-serialized format.
+   LightPayload has a smaller message size than JSON or even MessagePack, because it is optimised for the data send
+   and received by tornado rather than being a generic data format. Packing and unpacking uses string operations
+   mostly, making it fast and small.
+"""
+
+from interface import implements
+from .format import MessageFormat
+
+class LightPayloadMessageFormat(implements(MessageFormat)):
+    """Message format implementation writing and reading data as custom string-serialized format.
+       LightPayload has a smaller message size than JSON or even MessagePack, because it is optimised for the data send
+       and received by tornado rather than being a generic data format. Packing and unpacking uses string operations
+       mostly, making it fast and small.
+    """
+
+    # forward map mapping terminado types to LightPayload types
+    TYPES = {
+        "stdin": "I",        # (I)nput
+        "stdout": "O",       # (O)utput
+        "set_size": "S",     # set (S)ize
+        "setup": "C",        # (C)onnect
+        "disconnect": "D",   # (D)isconnect
+        "switch_format": "F" # switch (F)ormat
+    }
+
+    # reverse map mapping LightPayload types to terminado types
+    RTYPES = {value:key for key, value in TYPES.items()}
+
+    @staticmethod
+    def pack(command: str, message):
+        """Pack the given command and message for writing to the socket."""
+        # map the terminado type to the corresponding LightPayload type
+        command = LightPayloadMessageFormat.TYPES[command]
+
+        pack = command + "|"
+
+        if isinstance(message, list):
+            pack += ",".join(message)
+        else:
+            pack += message or ""
+
+        return pack
+
+    @staticmethod
+    def unpack(data) -> list:
+        """Unpack the data read from the socket."""
+        # map the LightPayload type to the corresponding terminado type
+        command = LightPayloadMessageFormat.RTYPES[data[0]]
+
+        message = data[2:]
+
+        # the message is always a string, except for "set_size" for which it is a stringyfied list of (two) ints
+        if command == "set_size":
+            message = [int(x) for x in message.split(',')]
+            message = [command] + message
+        else:
+            message = [command, message]
+
+        return message
diff --git a/terminado/formats/messagepack.py b/terminado/formats/messagepack.py
new file mode 100644
index 0000000..223c9cd
--- /dev/null
+++ b/terminado/formats/messagepack.py
@@ -0,0 +1,54 @@
+"""Message format implementation writing and reading data as MessagePack.
+   See https://msgpack.org for MessagePack.
+   It's like JSON. but fast and small.
+"""
+
+import msgpack
+from interface import implements
+from .format import MessageFormat
+
+class MessagePackMessageFormat(implements(MessageFormat)):
+    """Message format implementation writing and reading data as MessagePack.
+       See https://msgpack.org for MessagePack.
+       It's like JSON. but fast and small.
+    """
+
+    # forward map mapping terminado types to LightPayload types
+    TYPES = {
+        "stdin": 1,
+        "stdout": 2,
+        "set_size": 3,
+        "setup": 4,
+        "disconnect": 5,
+        "switch_format": 6
+    }
+
+    # reverse map mapping LightPayload types to terminado types
+    RTYPES = {str(value):key for key, value in TYPES.items()}
+
+    @staticmethod
+    def pack(command: str, message):
+        """Pack the given command and message for writing to the socket."""
+        # map the terminado type to the corresponding MessagePack type
+        command = MessagePackMessageFormat.TYPES[command]
+
+        pack = [command]
+
+        if isinstance(message, list):
+            pack = pack + message
+        else:
+            pack.append(message)
+
+        # use an UTF-8 encoded string instead of bytes
+        return msgpack.dumps(pack, use_bin_type=False)
+
+    @staticmethod
+    def unpack(data) -> list:
+        """Unpack the data read from the socket."""
+        pack = msgpack.loads(data, raw=False)
+
+        # map the MessagePack type to the corresponding terminado type
+        command = MessagePackMessageFormat.RTYPES[str(pack[0])]
+        pack[0] = command
+
+        return pack
diff --git a/terminado/websocket.py b/terminado/websocket.py
index 3d3fe39..e2745af 100644
--- a/terminado/websocket.py
+++ b/terminado/websocket.py
@@ -12,8 +12,8 @@
 except ImportError:
     from urlparse import urlparse
 
-import json
 import logging
+import importlib
 
 import tornado.web
 import tornado.websocket
@@ -24,12 +24,26 @@ def _cast_unicode(s):
     return s
 
 class TermSocket(tornado.websocket.WebSocketHandler):
+    # map mapping message format identifiers to their implementing classes
+    MESSAGE_FORMATS = {
+        "JSON": "JSONMessageFormat",
+        "LightPayload": "LightPayloadMessageFormat",
+        "MessagePack": "MessagePackMessageFormat"
+    }
+
+    def get_compression_options(self):
+        """Use the WebSocket's permessage-deflate extension."""
+        return {}
+
     """Handler for a terminal websocket"""
-    def initialize(self, term_manager):
+    def initialize(self, term_manager, message_format = "JSON"):
         self.term_manager = term_manager
         self.term_name = ""
         self.size = (None, None)
         self.terminal = None
+        # load the class implementing the message format
+        self.message_format = getattr(importlib.import_module("terminado.formats." + message_format.lower()),
+                                      self.MESSAGE_FORMATS[message_format])
 
         self._logger = logging.getLogger(__name__)
 
@@ -56,31 +70,41 @@ def open(self, url_component=None):
             self.on_pty_read(s)
         self.terminal.clients.append(self)
 
-        self.send_json_message(["setup", {}])
+        self.send_message("setup", {})
         self._logger.info("TermSocket.open: Opened %s", self.term_name)
 
+    def send_message(self, command, message):
+        """Sends a typed message packed by the current message format implementation."""
+
+        pack = self.message_format.pack(command, message)
+
+        # make sure binary packs are send as binary
+        if hasattr(pack, "decode"):
+            self.write_message(pack, binary=True)
+        else:
+            self.write_message(pack)
+
     def on_pty_read(self, text):
         """Data read from pty; send to frontend"""
-        self.send_json_message(['stdout', text])
+        self.send_message("stdout", text)
 
-    def send_json_message(self, content):
-        json_msg = json.dumps(content)
-        self.write_message(json_msg)
-
-    def on_message(self, message):
+    def on_message(self, pack):
         """Handle incoming websocket message
-        
-        We send JSON arrays, where the first element is a string indicating
-        what kind of message this is. Data associated with the message follows.
         """
         ##logging.info("TermSocket.on_message: %s - (%s) %s", self.term_name, type(message), len(message) if isinstance(message, bytes) else message[:250])
-        command = json.loads(message)
-        msg_type = command[0]    
-
-        if msg_type == "stdin":
-            self.terminal.ptyproc.write(command[1])
-        elif msg_type == "set_size":
-            self.size = command[1:3]
+        message = self.message_format.unpack(pack)
+
+        if message[0] == "switch_format":
+            # load the class implementing the message format
+            self.message_format = getattr(importlib.import_module("terminado.formats." + message[1].lower()),
+                                          self.MESSAGE_FORMATS[message[1]])
+
+            for s in self.terminal.read_buffer:
+                self.on_pty_read(s)
+        elif message[0] == "stdin":
+            self.terminal.ptyproc.write(message[1])
+        elif message[0] == "set_size":
+            self.size = message[1:3]
             self.terminal.resize_to_smallest()
 
     def on_close(self):
@@ -98,6 +122,7 @@ def on_close(self):
     def on_pty_died(self):
         """Terminal closed: tell the frontend, and close the socket.
         """
-        self.send_json_message(['disconnect', 1])
+        self.send_message("disconnect", 1)
+
         self.close()
         self.terminal = None