diff --git a/demo_websockets/package-lock.json b/demo_websockets/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..b2baf806f300555246a9bab2785b766f2d142b23
--- /dev/null
+++ b/demo_websockets/package-lock.json
@@ -0,0 +1,1018 @@
+{
+  "name": "websockets",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "dependencies": {
+        "express": "^4.19.2",
+        "socket.io": "^4.7.5"
+      }
+    },
+    "node_modules/@socket.io/component-emitter": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+      "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
+      "license": "MIT"
+    },
+    "node_modules/@types/cookie": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
+      "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
+      "license": "MIT"
+    },
+    "node_modules/@types/cors": {
+      "version": "2.8.17",
+      "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
+      "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/node": {
+      "version": "22.2.0",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.2.0.tgz",
+      "integrity": "sha512-bm6EG6/pCpkxDf/0gDNDdtDILMOHgaQBVOJGdwsqClnxA3xL6jtMv76rLBc006RVMWbmaf0xbmom4Z/5o2nRkQ==",
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~6.13.0"
+      }
+    },
+    "node_modules/accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+      "license": "MIT"
+    },
+    "node_modules/base64id": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+      "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
+      "license": "MIT",
+      "engines": {
+        "node": "^4.5.0 || >= 5.9"
+      }
+    },
+    "node_modules/body-parser": {
+      "version": "1.20.2",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
+      "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "3.1.2",
+        "content-type": "~1.0.5",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "on-finished": "2.4.1",
+        "qs": "6.11.0",
+        "raw-body": "2.5.2",
+        "type-is": "~1.6.18",
+        "unpipe": "1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/call-bind": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
+      "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+      "license": "MIT",
+      "dependencies": {
+        "es-define-property": "^1.0.0",
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2",
+        "get-intrinsic": "^1.2.4",
+        "set-function-length": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/content-disposition": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "5.2.1"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/content-type": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+      "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+      "license": "MIT"
+    },
+    "node_modules/cors": {
+      "version": "2.8.5",
+      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+      "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+      "license": "MIT",
+      "dependencies": {
+        "object-assign": "^4",
+        "vary": "^1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/define-data-property": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+      "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+      "license": "MIT",
+      "dependencies": {
+        "es-define-property": "^1.0.0",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/destroy": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+      "license": "MIT"
+    },
+    "node_modules/encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/engine.io": {
+      "version": "6.5.5",
+      "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz",
+      "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/cookie": "^0.4.1",
+        "@types/cors": "^2.8.12",
+        "@types/node": ">=10.0.0",
+        "accepts": "~1.3.4",
+        "base64id": "2.0.0",
+        "cookie": "~0.4.1",
+        "cors": "~2.8.5",
+        "debug": "~4.3.1",
+        "engine.io-parser": "~5.2.1",
+        "ws": "~8.17.1"
+      },
+      "engines": {
+        "node": ">=10.2.0"
+      }
+    },
+    "node_modules/engine.io-parser": {
+      "version": "5.2.3",
+      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+      "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/engine.io/node_modules/cookie": {
+      "version": "0.4.2",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
+      "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/engine.io/node_modules/debug": {
+      "version": "4.3.6",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
+      "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/engine.io/node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "license": "MIT"
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+      "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+      "license": "MIT",
+      "dependencies": {
+        "get-intrinsic": "^1.2.4"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "license": "MIT"
+    },
+    "node_modules/etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/express": {
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
+      "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
+      "license": "MIT",
+      "dependencies": {
+        "accepts": "~1.3.8",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.20.2",
+        "content-disposition": "0.5.4",
+        "content-type": "~1.0.4",
+        "cookie": "0.6.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "1.2.0",
+        "fresh": "0.5.2",
+        "http-errors": "2.0.0",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "2.4.1",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.7",
+        "qs": "6.11.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.2.1",
+        "send": "0.18.0",
+        "serve-static": "1.15.0",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.10.0"
+      }
+    },
+    "node_modules/finalhandler": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+      "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "2.4.1",
+        "parseurl": "~1.3.3",
+        "statuses": "2.0.1",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+      "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2",
+        "has-proto": "^1.0.1",
+        "has-symbols": "^1.0.3",
+        "hasown": "^2.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+      "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+      "license": "MIT",
+      "dependencies": {
+        "get-intrinsic": "^1.1.3"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-property-descriptors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+      "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+      "license": "MIT",
+      "dependencies": {
+        "es-define-property": "^1.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-proto": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+      "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/http-errors": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+      "license": "MIT",
+      "dependencies": {
+        "depd": "2.0.0",
+        "inherits": "2.0.4",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "toidentifier": "1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "license": "ISC"
+    },
+    "node_modules/ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==",
+      "license": "MIT"
+    },
+    "node_modules/methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "license": "MIT",
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+      "license": "MIT"
+    },
+    "node_modules/negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/object-inspect": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
+      "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/on-finished": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+      "license": "MIT",
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==",
+      "license": "MIT"
+    },
+    "node_modules/proxy-addr": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "license": "MIT",
+      "dependencies": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/qs": {
+      "version": "6.11.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+      "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "side-channel": "^1.0.4"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/raw-body": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+      "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "3.1.2",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "license": "MIT"
+    },
+    "node_modules/send": {
+      "version": "0.18.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+      "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "2.0.0",
+        "mime": "1.6.0",
+        "ms": "2.1.3",
+        "on-finished": "2.4.1",
+        "range-parser": "~1.2.1",
+        "statuses": "2.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/send/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/serve-static": {
+      "version": "1.15.0",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+      "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+      "license": "MIT",
+      "dependencies": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.18.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/set-function-length": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+      "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+      "license": "MIT",
+      "dependencies": {
+        "define-data-property": "^1.1.4",
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2",
+        "get-intrinsic": "^1.2.4",
+        "gopd": "^1.0.1",
+        "has-property-descriptors": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+      "license": "ISC"
+    },
+    "node_modules/side-channel": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
+      "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind": "^1.0.7",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.4",
+        "object-inspect": "^1.13.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/socket.io": {
+      "version": "4.7.5",
+      "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz",
+      "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==",
+      "license": "MIT",
+      "dependencies": {
+        "accepts": "~1.3.4",
+        "base64id": "~2.0.0",
+        "cors": "~2.8.5",
+        "debug": "~4.3.2",
+        "engine.io": "~6.5.2",
+        "socket.io-adapter": "~2.5.2",
+        "socket.io-parser": "~4.2.4"
+      },
+      "engines": {
+        "node": ">=10.2.0"
+      }
+    },
+    "node_modules/socket.io-adapter": {
+      "version": "2.5.5",
+      "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
+      "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "~4.3.4",
+        "ws": "~8.17.1"
+      }
+    },
+    "node_modules/socket.io-adapter/node_modules/debug": {
+      "version": "4.3.6",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
+      "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/socket.io-adapter/node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "license": "MIT"
+    },
+    "node_modules/socket.io-parser": {
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+      "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+      "license": "MIT",
+      "dependencies": {
+        "@socket.io/component-emitter": "~3.1.0",
+        "debug": "~4.3.1"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/socket.io-parser/node_modules/debug": {
+      "version": "4.3.6",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
+      "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/socket.io-parser/node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "license": "MIT"
+    },
+    "node_modules/socket.io/node_modules/debug": {
+      "version": "4.3.6",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
+      "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/socket.io/node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "license": "MIT"
+    },
+    "node_modules/statuses": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "license": "MIT",
+      "dependencies": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/undici-types": {
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz",
+      "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==",
+      "license": "MIT"
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/ws": {
+      "version": "8.17.1",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+      "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    }
+  }
+}
diff --git a/demo_websockets/package.json b/demo_websockets/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..3f444109a61cb9c16ef8eb2e31076609dde98523
--- /dev/null
+++ b/demo_websockets/package.json
@@ -0,0 +1,9 @@
+{
+  "scripts": {
+    "start": "node server.js"
+  },
+  "dependencies": {
+    "express": "^4.19.2",
+    "socket.io": "^4.7.5"
+  }
+}
diff --git a/demo_websockets/public/index.html b/demo_websockets/public/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..51221d8f5d13ed34e62e56f8b122cefade267391
--- /dev/null
+++ b/demo_websockets/public/index.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Demo</title>
+</head>
+<body>
+  <button id="send">Create chatroom</button>
+  <div id="ids"></div>
+
+  <!-- no sockets on this page - we don't need 'em
+  we just get the room code with good ol' Ajax -->
+
+  <script>
+    let button = document.getElementById("send");
+    let idsDiv = document.getElementById("ids");
+    button.addEventListener("click", async () => {
+      let response = await fetch("/create", { method: "POST" })
+      let { roomId } = await response.json();
+      // will redirect to new chatroom immediately
+      window.location = `/room/${roomId}`;
+    });
+  </script>
+</body>
+</html>
diff --git a/demo_websockets/public/room.html b/demo_websockets/public/room.html
new file mode 100644
index 0000000000000000000000000000000000000000..bedabbcc7551c545b8eece33085770ec446a23e8
--- /dev/null
+++ b/demo_websockets/public/room.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Demo</title>
+</head>
+<body>
+  <input id="input">
+  <button id="send">Send</button>
+  <div id="messages"></div>
+
+  <!-- MUST INCLUDE SOCKET LIBRARY HERE -->
+  <!-- REMEMBER TO COPY THIS FROM socket.io's node_modules INTO public -->
+  <script src="/socket.io/socket.io.js"></script>
+
+  <script>
+    // extract room ID from URL
+    // if we did server side rendering, server could embed it in the JS for us
+    let pathParts = window.location.pathname.split("/");
+    let roomId = pathParts[pathParts.length - 1] ;
+
+    function appendMessage(message) {
+      let item = document.createElement("div");
+      item.textContent = message;
+      messagesDiv.appendChild(item);
+    }
+
+    let socket = io();
+    // WARNING: socket.connected will NOT be true immediately
+    // if you try to immediately emit an event
+    // it'll wait until it's connected, then send - that's fine
+    socket.emit("hello", "world"); // works
+    // but if you wanted to e.g. do something with the socket id
+    // it will be undefined until the socket connects
+    console.log(socket.id); // undefined, b/c hasn't connected yet
+    // so if you need the id for some reason
+    // you can register an event listener for the connected event:
+    socket.on("connect", () => { console.log("I am connected", socket.id); });
+
+    let button = document.getElementById("send");
+    let input = document.getElementById("input");
+    let messagesDiv = document.getElementById("messages");
+
+    button.addEventListener("click", () => {
+      let message = input.value;
+      if (message === "") {
+        return;
+      }
+      console.log("Sending message:", message);
+      // socket.emit can send a string, object, array, etc.
+      socket.emit("foo", { message });
+      // need to append its own message, as it won't receive its own event
+      appendMessage(message);
+    });
+
+    /* MUST REGISTER socket.on(event) listener FOR EVERY event SERVER CAN SEND */
+
+    socket.on("bar", function(data) {
+      console.log("Received message:", data);
+      appendMessage(data);
+    });
+  </script>
+</body>
+</html>
diff --git a/demo_websockets/public/socket.io.js b/demo_websockets/public/socket.io.js
new file mode 100644
index 0000000000000000000000000000000000000000..2d84e28e0f7d709eaf3a50548f8e359977f4b9e0
--- /dev/null
+++ b/demo_websockets/public/socket.io.js
@@ -0,0 +1,5015 @@
+/*!
+ * Socket.IO v4.7.5
+ * (c) 2014-2024 Guillermo Rauch
+ * Released under the MIT License.
+ */
+(function (global, factory) {
+  typeof exports === "object" && typeof module !== "undefined"
+    ? (module.exports = factory())
+    : typeof define === "function" && define.amd
+      ? define(factory)
+      : ((global =
+          typeof globalThis !== "undefined" ? globalThis : global || self),
+        (global.io = factory()));
+})(this, function () {
+  "use strict";
+
+  function _typeof(obj) {
+    "@babel/helpers - typeof";
+
+    return (
+      (_typeof =
+        "function" == typeof Symbol && "symbol" == typeof Symbol.iterator
+          ? function (obj) {
+              return typeof obj;
+            }
+          : function (obj) {
+              return obj &&
+                "function" == typeof Symbol &&
+                obj.constructor === Symbol &&
+                obj !== Symbol.prototype
+                ? "symbol"
+                : typeof obj;
+            }),
+      _typeof(obj)
+    );
+  }
+  function _classCallCheck(instance, Constructor) {
+    if (!(instance instanceof Constructor)) {
+      throw new TypeError("Cannot call a class as a function");
+    }
+  }
+  function _defineProperties(target, props) {
+    for (var i = 0; i < props.length; i++) {
+      var descriptor = props[i];
+      descriptor.enumerable = descriptor.enumerable || false;
+      descriptor.configurable = true;
+      if ("value" in descriptor) descriptor.writable = true;
+      Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
+    }
+  }
+  function _createClass(Constructor, protoProps, staticProps) {
+    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
+    if (staticProps) _defineProperties(Constructor, staticProps);
+    Object.defineProperty(Constructor, "prototype", {
+      writable: false,
+    });
+    return Constructor;
+  }
+  function _extends() {
+    _extends = Object.assign
+      ? Object.assign.bind()
+      : function (target) {
+          for (var i = 1; i < arguments.length; i++) {
+            var source = arguments[i];
+            for (var key in source) {
+              if (Object.prototype.hasOwnProperty.call(source, key)) {
+                target[key] = source[key];
+              }
+            }
+          }
+          return target;
+        };
+    return _extends.apply(this, arguments);
+  }
+  function _inherits(subClass, superClass) {
+    if (typeof superClass !== "function" && superClass !== null) {
+      throw new TypeError("Super expression must either be null or a function");
+    }
+    subClass.prototype = Object.create(superClass && superClass.prototype, {
+      constructor: {
+        value: subClass,
+        writable: true,
+        configurable: true,
+      },
+    });
+    Object.defineProperty(subClass, "prototype", {
+      writable: false,
+    });
+    if (superClass) _setPrototypeOf(subClass, superClass);
+  }
+  function _getPrototypeOf(o) {
+    _getPrototypeOf = Object.setPrototypeOf
+      ? Object.getPrototypeOf.bind()
+      : function _getPrototypeOf(o) {
+          return o.__proto__ || Object.getPrototypeOf(o);
+        };
+    return _getPrototypeOf(o);
+  }
+  function _setPrototypeOf(o, p) {
+    _setPrototypeOf = Object.setPrototypeOf
+      ? Object.setPrototypeOf.bind()
+      : function _setPrototypeOf(o, p) {
+          o.__proto__ = p;
+          return o;
+        };
+    return _setPrototypeOf(o, p);
+  }
+  function _isNativeReflectConstruct() {
+    if (typeof Reflect === "undefined" || !Reflect.construct) return false;
+    if (Reflect.construct.sham) return false;
+    if (typeof Proxy === "function") return true;
+    try {
+      Boolean.prototype.valueOf.call(
+        Reflect.construct(Boolean, [], function () {}),
+      );
+      return true;
+    } catch (e) {
+      return false;
+    }
+  }
+  function _construct(Parent, args, Class) {
+    if (_isNativeReflectConstruct()) {
+      _construct = Reflect.construct.bind();
+    } else {
+      _construct = function _construct(Parent, args, Class) {
+        var a = [null];
+        a.push.apply(a, args);
+        var Constructor = Function.bind.apply(Parent, a);
+        var instance = new Constructor();
+        if (Class) _setPrototypeOf(instance, Class.prototype);
+        return instance;
+      };
+    }
+    return _construct.apply(null, arguments);
+  }
+  function _isNativeFunction(fn) {
+    return Function.toString.call(fn).indexOf("[native code]") !== -1;
+  }
+  function _wrapNativeSuper(Class) {
+    var _cache = typeof Map === "function" ? new Map() : undefined;
+    _wrapNativeSuper = function _wrapNativeSuper(Class) {
+      if (Class === null || !_isNativeFunction(Class)) return Class;
+      if (typeof Class !== "function") {
+        throw new TypeError(
+          "Super expression must either be null or a function",
+        );
+      }
+      if (typeof _cache !== "undefined") {
+        if (_cache.has(Class)) return _cache.get(Class);
+        _cache.set(Class, Wrapper);
+      }
+      function Wrapper() {
+        return _construct(Class, arguments, _getPrototypeOf(this).constructor);
+      }
+      Wrapper.prototype = Object.create(Class.prototype, {
+        constructor: {
+          value: Wrapper,
+          enumerable: false,
+          writable: true,
+          configurable: true,
+        },
+      });
+      return _setPrototypeOf(Wrapper, Class);
+    };
+    return _wrapNativeSuper(Class);
+  }
+  function _assertThisInitialized(self) {
+    if (self === void 0) {
+      throw new ReferenceError(
+        "this hasn't been initialised - super() hasn't been called",
+      );
+    }
+    return self;
+  }
+  function _possibleConstructorReturn(self, call) {
+    if (call && (typeof call === "object" || typeof call === "function")) {
+      return call;
+    } else if (call !== void 0) {
+      throw new TypeError(
+        "Derived constructors may only return object or undefined",
+      );
+    }
+    return _assertThisInitialized(self);
+  }
+  function _createSuper(Derived) {
+    var hasNativeReflectConstruct = _isNativeReflectConstruct();
+    return function _createSuperInternal() {
+      var Super = _getPrototypeOf(Derived),
+        result;
+      if (hasNativeReflectConstruct) {
+        var NewTarget = _getPrototypeOf(this).constructor;
+        result = Reflect.construct(Super, arguments, NewTarget);
+      } else {
+        result = Super.apply(this, arguments);
+      }
+      return _possibleConstructorReturn(this, result);
+    };
+  }
+  function _superPropBase(object, property) {
+    while (!Object.prototype.hasOwnProperty.call(object, property)) {
+      object = _getPrototypeOf(object);
+      if (object === null) break;
+    }
+    return object;
+  }
+  function _get() {
+    if (typeof Reflect !== "undefined" && Reflect.get) {
+      _get = Reflect.get.bind();
+    } else {
+      _get = function _get(target, property, receiver) {
+        var base = _superPropBase(target, property);
+        if (!base) return;
+        var desc = Object.getOwnPropertyDescriptor(base, property);
+        if (desc.get) {
+          return desc.get.call(arguments.length < 3 ? target : receiver);
+        }
+        return desc.value;
+      };
+    }
+    return _get.apply(this, arguments);
+  }
+  function _unsupportedIterableToArray(o, minLen) {
+    if (!o) return;
+    if (typeof o === "string") return _arrayLikeToArray(o, minLen);
+    var n = Object.prototype.toString.call(o).slice(8, -1);
+    if (n === "Object" && o.constructor) n = o.constructor.name;
+    if (n === "Map" || n === "Set") return Array.from(o);
+    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
+      return _arrayLikeToArray(o, minLen);
+  }
+  function _arrayLikeToArray(arr, len) {
+    if (len == null || len > arr.length) len = arr.length;
+    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
+    return arr2;
+  }
+  function _createForOfIteratorHelper(o, allowArrayLike) {
+    var it =
+      (typeof Symbol !== "undefined" && o[Symbol.iterator]) || o["@@iterator"];
+    if (!it) {
+      if (
+        Array.isArray(o) ||
+        (it = _unsupportedIterableToArray(o)) ||
+        (allowArrayLike && o && typeof o.length === "number")
+      ) {
+        if (it) o = it;
+        var i = 0;
+        var F = function () {};
+        return {
+          s: F,
+          n: function () {
+            if (i >= o.length)
+              return {
+                done: true,
+              };
+            return {
+              done: false,
+              value: o[i++],
+            };
+          },
+          e: function (e) {
+            throw e;
+          },
+          f: F,
+        };
+      }
+      throw new TypeError(
+        "Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.",
+      );
+    }
+    var normalCompletion = true,
+      didErr = false,
+      err;
+    return {
+      s: function () {
+        it = it.call(o);
+      },
+      n: function () {
+        var step = it.next();
+        normalCompletion = step.done;
+        return step;
+      },
+      e: function (e) {
+        didErr = true;
+        err = e;
+      },
+      f: function () {
+        try {
+          if (!normalCompletion && it.return != null) it.return();
+        } finally {
+          if (didErr) throw err;
+        }
+      },
+    };
+  }
+  function _toPrimitive(input, hint) {
+    if (typeof input !== "object" || input === null) return input;
+    var prim = input[Symbol.toPrimitive];
+    if (prim !== undefined) {
+      var res = prim.call(input, hint || "default");
+      if (typeof res !== "object") return res;
+      throw new TypeError("@@toPrimitive must return a primitive value.");
+    }
+    return (hint === "string" ? String : Number)(input);
+  }
+  function _toPropertyKey(arg) {
+    var key = _toPrimitive(arg, "string");
+    return typeof key === "symbol" ? key : String(key);
+  }
+
+  var PACKET_TYPES = Object.create(null); // no Map = no polyfill
+  PACKET_TYPES["open"] = "0";
+  PACKET_TYPES["close"] = "1";
+  PACKET_TYPES["ping"] = "2";
+  PACKET_TYPES["pong"] = "3";
+  PACKET_TYPES["message"] = "4";
+  PACKET_TYPES["upgrade"] = "5";
+  PACKET_TYPES["noop"] = "6";
+  var PACKET_TYPES_REVERSE = Object.create(null);
+  Object.keys(PACKET_TYPES).forEach(function (key) {
+    PACKET_TYPES_REVERSE[PACKET_TYPES[key]] = key;
+  });
+  var ERROR_PACKET = {
+    type: "error",
+    data: "parser error",
+  };
+
+  var withNativeBlob$1 =
+    typeof Blob === "function" ||
+    (typeof Blob !== "undefined" &&
+      Object.prototype.toString.call(Blob) === "[object BlobConstructor]");
+  var withNativeArrayBuffer$2 = typeof ArrayBuffer === "function";
+  // ArrayBuffer.isView method is not defined in IE10
+  var isView$1 = function isView(obj) {
+    return typeof ArrayBuffer.isView === "function"
+      ? ArrayBuffer.isView(obj)
+      : obj && obj.buffer instanceof ArrayBuffer;
+  };
+  var encodePacket = function encodePacket(_ref, supportsBinary, callback) {
+    var type = _ref.type,
+      data = _ref.data;
+    if (withNativeBlob$1 && data instanceof Blob) {
+      if (supportsBinary) {
+        return callback(data);
+      } else {
+        return encodeBlobAsBase64(data, callback);
+      }
+    } else if (
+      withNativeArrayBuffer$2 &&
+      (data instanceof ArrayBuffer || isView$1(data))
+    ) {
+      if (supportsBinary) {
+        return callback(data);
+      } else {
+        return encodeBlobAsBase64(new Blob([data]), callback);
+      }
+    }
+    // plain string
+    return callback(PACKET_TYPES[type] + (data || ""));
+  };
+  var encodeBlobAsBase64 = function encodeBlobAsBase64(data, callback) {
+    var fileReader = new FileReader();
+    fileReader.onload = function () {
+      var content = fileReader.result.split(",")[1];
+      callback("b" + (content || ""));
+    };
+    return fileReader.readAsDataURL(data);
+  };
+  function toArray(data) {
+    if (data instanceof Uint8Array) {
+      return data;
+    } else if (data instanceof ArrayBuffer) {
+      return new Uint8Array(data);
+    } else {
+      return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
+    }
+  }
+  var TEXT_ENCODER;
+  function encodePacketToBinary(packet, callback) {
+    if (withNativeBlob$1 && packet.data instanceof Blob) {
+      return packet.data.arrayBuffer().then(toArray).then(callback);
+    } else if (
+      withNativeArrayBuffer$2 &&
+      (packet.data instanceof ArrayBuffer || isView$1(packet.data))
+    ) {
+      return callback(toArray(packet.data));
+    }
+    encodePacket(packet, false, function (encoded) {
+      if (!TEXT_ENCODER) {
+        TEXT_ENCODER = new TextEncoder();
+      }
+      callback(TEXT_ENCODER.encode(encoded));
+    });
+  }
+
+  // imported from https://github.com/socketio/base64-arraybuffer
+  var chars =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  // Use a lookup table to find the index.
+  var lookup$1 = typeof Uint8Array === "undefined" ? [] : new Uint8Array(256);
+  for (var i$1 = 0; i$1 < chars.length; i$1++) {
+    lookup$1[chars.charCodeAt(i$1)] = i$1;
+  }
+  var decode$1 = function decode(base64) {
+    var bufferLength = base64.length * 0.75,
+      len = base64.length,
+      i,
+      p = 0,
+      encoded1,
+      encoded2,
+      encoded3,
+      encoded4;
+    if (base64[base64.length - 1] === "=") {
+      bufferLength--;
+      if (base64[base64.length - 2] === "=") {
+        bufferLength--;
+      }
+    }
+    var arraybuffer = new ArrayBuffer(bufferLength),
+      bytes = new Uint8Array(arraybuffer);
+    for (i = 0; i < len; i += 4) {
+      encoded1 = lookup$1[base64.charCodeAt(i)];
+      encoded2 = lookup$1[base64.charCodeAt(i + 1)];
+      encoded3 = lookup$1[base64.charCodeAt(i + 2)];
+      encoded4 = lookup$1[base64.charCodeAt(i + 3)];
+      bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
+      bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
+      bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
+    }
+    return arraybuffer;
+  };
+
+  var withNativeArrayBuffer$1 = typeof ArrayBuffer === "function";
+  var decodePacket = function decodePacket(encodedPacket, binaryType) {
+    if (typeof encodedPacket !== "string") {
+      return {
+        type: "message",
+        data: mapBinary(encodedPacket, binaryType),
+      };
+    }
+    var type = encodedPacket.charAt(0);
+    if (type === "b") {
+      return {
+        type: "message",
+        data: decodeBase64Packet(encodedPacket.substring(1), binaryType),
+      };
+    }
+    var packetType = PACKET_TYPES_REVERSE[type];
+    if (!packetType) {
+      return ERROR_PACKET;
+    }
+    return encodedPacket.length > 1
+      ? {
+          type: PACKET_TYPES_REVERSE[type],
+          data: encodedPacket.substring(1),
+        }
+      : {
+          type: PACKET_TYPES_REVERSE[type],
+        };
+  };
+  var decodeBase64Packet = function decodeBase64Packet(data, binaryType) {
+    if (withNativeArrayBuffer$1) {
+      var decoded = decode$1(data);
+      return mapBinary(decoded, binaryType);
+    } else {
+      return {
+        base64: true,
+        data: data,
+      }; // fallback for old browsers
+    }
+  };
+
+  var mapBinary = function mapBinary(data, binaryType) {
+    switch (binaryType) {
+      case "blob":
+        if (data instanceof Blob) {
+          // from WebSocket + binaryType "blob"
+          return data;
+        } else {
+          // from HTTP long-polling or WebTransport
+          return new Blob([data]);
+        }
+      case "arraybuffer":
+      default:
+        if (data instanceof ArrayBuffer) {
+          // from HTTP long-polling (base64) or WebSocket + binaryType "arraybuffer"
+          return data;
+        } else {
+          // from WebTransport (Uint8Array)
+          return data.buffer;
+        }
+    }
+  };
+
+  var SEPARATOR = String.fromCharCode(30); // see https://en.wikipedia.org/wiki/Delimiter#ASCII_delimited_text
+  var encodePayload = function encodePayload(packets, callback) {
+    // some packets may be added to the array while encoding, so the initial length must be saved
+    var length = packets.length;
+    var encodedPackets = new Array(length);
+    var count = 0;
+    packets.forEach(function (packet, i) {
+      // force base64 encoding for binary packets
+      encodePacket(packet, false, function (encodedPacket) {
+        encodedPackets[i] = encodedPacket;
+        if (++count === length) {
+          callback(encodedPackets.join(SEPARATOR));
+        }
+      });
+    });
+  };
+  var decodePayload = function decodePayload(encodedPayload, binaryType) {
+    var encodedPackets = encodedPayload.split(SEPARATOR);
+    var packets = [];
+    for (var i = 0; i < encodedPackets.length; i++) {
+      var decodedPacket = decodePacket(encodedPackets[i], binaryType);
+      packets.push(decodedPacket);
+      if (decodedPacket.type === "error") {
+        break;
+      }
+    }
+    return packets;
+  };
+  function createPacketEncoderStream() {
+    return new TransformStream({
+      transform: function transform(packet, controller) {
+        encodePacketToBinary(packet, function (encodedPacket) {
+          var payloadLength = encodedPacket.length;
+          var header;
+          // inspired by the WebSocket format: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#decoding_payload_length
+          if (payloadLength < 126) {
+            header = new Uint8Array(1);
+            new DataView(header.buffer).setUint8(0, payloadLength);
+          } else if (payloadLength < 65536) {
+            header = new Uint8Array(3);
+            var view = new DataView(header.buffer);
+            view.setUint8(0, 126);
+            view.setUint16(1, payloadLength);
+          } else {
+            header = new Uint8Array(9);
+            var _view = new DataView(header.buffer);
+            _view.setUint8(0, 127);
+            _view.setBigUint64(1, BigInt(payloadLength));
+          }
+          // first bit indicates whether the payload is plain text (0) or binary (1)
+          if (packet.data && typeof packet.data !== "string") {
+            header[0] |= 0x80;
+          }
+          controller.enqueue(header);
+          controller.enqueue(encodedPacket);
+        });
+      },
+    });
+  }
+  var TEXT_DECODER;
+  function totalLength(chunks) {
+    return chunks.reduce(function (acc, chunk) {
+      return acc + chunk.length;
+    }, 0);
+  }
+  function concatChunks(chunks, size) {
+    if (chunks[0].length === size) {
+      return chunks.shift();
+    }
+    var buffer = new Uint8Array(size);
+    var j = 0;
+    for (var i = 0; i < size; i++) {
+      buffer[i] = chunks[0][j++];
+      if (j === chunks[0].length) {
+        chunks.shift();
+        j = 0;
+      }
+    }
+    if (chunks.length && j < chunks[0].length) {
+      chunks[0] = chunks[0].slice(j);
+    }
+    return buffer;
+  }
+  function createPacketDecoderStream(maxPayload, binaryType) {
+    if (!TEXT_DECODER) {
+      TEXT_DECODER = new TextDecoder();
+    }
+    var chunks = [];
+    var state = 0; /* READ_HEADER */
+    var expectedLength = -1;
+    var isBinary = false;
+    return new TransformStream({
+      transform: function transform(chunk, controller) {
+        chunks.push(chunk);
+        while (true) {
+          if (state === 0 /* READ_HEADER */) {
+            if (totalLength(chunks) < 1) {
+              break;
+            }
+            var header = concatChunks(chunks, 1);
+            isBinary = (header[0] & 0x80) === 0x80;
+            expectedLength = header[0] & 0x7f;
+            if (expectedLength < 126) {
+              state = 3 /* READ_PAYLOAD */;
+            } else if (expectedLength === 126) {
+              state = 1 /* READ_EXTENDED_LENGTH_16 */;
+            } else {
+              state = 2 /* READ_EXTENDED_LENGTH_64 */;
+            }
+          } else if (state === 1 /* READ_EXTENDED_LENGTH_16 */) {
+            if (totalLength(chunks) < 2) {
+              break;
+            }
+            var headerArray = concatChunks(chunks, 2);
+            expectedLength = new DataView(
+              headerArray.buffer,
+              headerArray.byteOffset,
+              headerArray.length,
+            ).getUint16(0);
+            state = 3 /* READ_PAYLOAD */;
+          } else if (state === 2 /* READ_EXTENDED_LENGTH_64 */) {
+            if (totalLength(chunks) < 8) {
+              break;
+            }
+            var _headerArray = concatChunks(chunks, 8);
+            var view = new DataView(
+              _headerArray.buffer,
+              _headerArray.byteOffset,
+              _headerArray.length,
+            );
+            var n = view.getUint32(0);
+            if (n > Math.pow(2, 53 - 32) - 1) {
+              // the maximum safe integer in JavaScript is 2^53 - 1
+              controller.enqueue(ERROR_PACKET);
+              break;
+            }
+            expectedLength = n * Math.pow(2, 32) + view.getUint32(4);
+            state = 3 /* READ_PAYLOAD */;
+          } else {
+            if (totalLength(chunks) < expectedLength) {
+              break;
+            }
+            var data = concatChunks(chunks, expectedLength);
+            controller.enqueue(
+              decodePacket(
+                isBinary ? data : TEXT_DECODER.decode(data),
+                binaryType,
+              ),
+            );
+            state = 0 /* READ_HEADER */;
+          }
+
+          if (expectedLength === 0 || expectedLength > maxPayload) {
+            controller.enqueue(ERROR_PACKET);
+            break;
+          }
+        }
+      },
+    });
+  }
+  var protocol$1 = 4;
+
+  /**
+   * Initialize a new `Emitter`.
+   *
+   * @api public
+   */
+
+  function Emitter(obj) {
+    if (obj) return mixin(obj);
+  }
+
+  /**
+   * Mixin the emitter properties.
+   *
+   * @param {Object} obj
+   * @return {Object}
+   * @api private
+   */
+
+  function mixin(obj) {
+    for (var key in Emitter.prototype) {
+      obj[key] = Emitter.prototype[key];
+    }
+    return obj;
+  }
+
+  /**
+   * Listen on the given `event` with `fn`.
+   *
+   * @param {String} event
+   * @param {Function} fn
+   * @return {Emitter}
+   * @api public
+   */
+
+  Emitter.prototype.on = Emitter.prototype.addEventListener = function (
+    event,
+    fn,
+  ) {
+    this._callbacks = this._callbacks || {};
+    (this._callbacks["$" + event] = this._callbacks["$" + event] || []).push(
+      fn,
+    );
+    return this;
+  };
+
+  /**
+   * Adds an `event` listener that will be invoked a single
+   * time then automatically removed.
+   *
+   * @param {String} event
+   * @param {Function} fn
+   * @return {Emitter}
+   * @api public
+   */
+
+  Emitter.prototype.once = function (event, fn) {
+    function on() {
+      this.off(event, on);
+      fn.apply(this, arguments);
+    }
+    on.fn = fn;
+    this.on(event, on);
+    return this;
+  };
+
+  /**
+   * Remove the given callback for `event` or all
+   * registered callbacks.
+   *
+   * @param {String} event
+   * @param {Function} fn
+   * @return {Emitter}
+   * @api public
+   */
+
+  Emitter.prototype.off =
+    Emitter.prototype.removeListener =
+    Emitter.prototype.removeAllListeners =
+    Emitter.prototype.removeEventListener =
+      function (event, fn) {
+        this._callbacks = this._callbacks || {};
+
+        // all
+        if (0 == arguments.length) {
+          this._callbacks = {};
+          return this;
+        }
+
+        // specific event
+        var callbacks = this._callbacks["$" + event];
+        if (!callbacks) return this;
+
+        // remove all handlers
+        if (1 == arguments.length) {
+          delete this._callbacks["$" + event];
+          return this;
+        }
+
+        // remove specific handler
+        var cb;
+        for (var i = 0; i < callbacks.length; i++) {
+          cb = callbacks[i];
+          if (cb === fn || cb.fn === fn) {
+            callbacks.splice(i, 1);
+            break;
+          }
+        }
+
+        // Remove event specific arrays for event types that no
+        // one is subscribed for to avoid memory leak.
+        if (callbacks.length === 0) {
+          delete this._callbacks["$" + event];
+        }
+        return this;
+      };
+
+  /**
+   * Emit `event` with the given args.
+   *
+   * @param {String} event
+   * @param {Mixed} ...
+   * @return {Emitter}
+   */
+
+  Emitter.prototype.emit = function (event) {
+    this._callbacks = this._callbacks || {};
+    var args = new Array(arguments.length - 1),
+      callbacks = this._callbacks["$" + event];
+    for (var i = 1; i < arguments.length; i++) {
+      args[i - 1] = arguments[i];
+    }
+    if (callbacks) {
+      callbacks = callbacks.slice(0);
+      for (var i = 0, len = callbacks.length; i < len; ++i) {
+        callbacks[i].apply(this, args);
+      }
+    }
+    return this;
+  };
+
+  // alias used for reserved events (protected method)
+  Emitter.prototype.emitReserved = Emitter.prototype.emit;
+
+  /**
+   * Return array of callbacks for `event`.
+   *
+   * @param {String} event
+   * @return {Array}
+   * @api public
+   */
+
+  Emitter.prototype.listeners = function (event) {
+    this._callbacks = this._callbacks || {};
+    return this._callbacks["$" + event] || [];
+  };
+
+  /**
+   * Check if this emitter has `event` handlers.
+   *
+   * @param {String} event
+   * @return {Boolean}
+   * @api public
+   */
+
+  Emitter.prototype.hasListeners = function (event) {
+    return !!this.listeners(event).length;
+  };
+
+  var globalThisShim = (function () {
+    if (typeof self !== "undefined") {
+      return self;
+    } else if (typeof window !== "undefined") {
+      return window;
+    } else {
+      return Function("return this")();
+    }
+  })();
+
+  function pick(obj) {
+    for (
+      var _len = arguments.length,
+        attr = new Array(_len > 1 ? _len - 1 : 0),
+        _key = 1;
+      _key < _len;
+      _key++
+    ) {
+      attr[_key - 1] = arguments[_key];
+    }
+    return attr.reduce(function (acc, k) {
+      if (obj.hasOwnProperty(k)) {
+        acc[k] = obj[k];
+      }
+      return acc;
+    }, {});
+  }
+  // Keep a reference to the real timeout functions so they can be used when overridden
+  var NATIVE_SET_TIMEOUT = globalThisShim.setTimeout;
+  var NATIVE_CLEAR_TIMEOUT = globalThisShim.clearTimeout;
+  function installTimerFunctions(obj, opts) {
+    if (opts.useNativeTimers) {
+      obj.setTimeoutFn = NATIVE_SET_TIMEOUT.bind(globalThisShim);
+      obj.clearTimeoutFn = NATIVE_CLEAR_TIMEOUT.bind(globalThisShim);
+    } else {
+      obj.setTimeoutFn = globalThisShim.setTimeout.bind(globalThisShim);
+      obj.clearTimeoutFn = globalThisShim.clearTimeout.bind(globalThisShim);
+    }
+  }
+  // base64 encoded buffers are about 33% bigger (https://en.wikipedia.org/wiki/Base64)
+  var BASE64_OVERHEAD = 1.33;
+  // we could also have used `new Blob([obj]).size`, but it isn't supported in IE9
+  function byteLength(obj) {
+    if (typeof obj === "string") {
+      return utf8Length(obj);
+    }
+    // arraybuffer or blob
+    return Math.ceil((obj.byteLength || obj.size) * BASE64_OVERHEAD);
+  }
+  function utf8Length(str) {
+    var c = 0,
+      length = 0;
+    for (var i = 0, l = str.length; i < l; i++) {
+      c = str.charCodeAt(i);
+      if (c < 0x80) {
+        length += 1;
+      } else if (c < 0x800) {
+        length += 2;
+      } else if (c < 0xd800 || c >= 0xe000) {
+        length += 3;
+      } else {
+        i++;
+        length += 4;
+      }
+    }
+    return length;
+  }
+
+  // imported from https://github.com/galkn/querystring
+  /**
+   * Compiles a querystring
+   * Returns string representation of the object
+   *
+   * @param {Object}
+   * @api private
+   */
+  function encode$1(obj) {
+    var str = "";
+    for (var i in obj) {
+      if (obj.hasOwnProperty(i)) {
+        if (str.length) str += "&";
+        str += encodeURIComponent(i) + "=" + encodeURIComponent(obj[i]);
+      }
+    }
+    return str;
+  }
+  /**
+   * Parses a simple querystring into an object
+   *
+   * @param {String} qs
+   * @api private
+   */
+  function decode(qs) {
+    var qry = {};
+    var pairs = qs.split("&");
+    for (var i = 0, l = pairs.length; i < l; i++) {
+      var pair = pairs[i].split("=");
+      qry[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
+    }
+    return qry;
+  }
+
+  var TransportError = /*#__PURE__*/ (function (_Error) {
+    _inherits(TransportError, _Error);
+    var _super = _createSuper(TransportError);
+    function TransportError(reason, description, context) {
+      var _this;
+      _classCallCheck(this, TransportError);
+      _this = _super.call(this, reason);
+      _this.description = description;
+      _this.context = context;
+      _this.type = "TransportError";
+      return _this;
+    }
+    return _createClass(TransportError);
+  })(/*#__PURE__*/ _wrapNativeSuper(Error));
+  var Transport = /*#__PURE__*/ (function (_Emitter) {
+    _inherits(Transport, _Emitter);
+    var _super2 = _createSuper(Transport);
+    /**
+     * Transport abstract constructor.
+     *
+     * @param {Object} opts - options
+     * @protected
+     */
+    function Transport(opts) {
+      var _this2;
+      _classCallCheck(this, Transport);
+      _this2 = _super2.call(this);
+      _this2.writable = false;
+      installTimerFunctions(_assertThisInitialized(_this2), opts);
+      _this2.opts = opts;
+      _this2.query = opts.query;
+      _this2.socket = opts.socket;
+      return _this2;
+    }
+    /**
+     * Emits an error.
+     *
+     * @param {String} reason
+     * @param description
+     * @param context - the error context
+     * @return {Transport} for chaining
+     * @protected
+     */
+    _createClass(Transport, [
+      {
+        key: "onError",
+        value: function onError(reason, description, context) {
+          _get(_getPrototypeOf(Transport.prototype), "emitReserved", this).call(
+            this,
+            "error",
+            new TransportError(reason, description, context),
+          );
+          return this;
+        },
+        /**
+         * Opens the transport.
+         */
+      },
+      {
+        key: "open",
+        value: function open() {
+          this.readyState = "opening";
+          this.doOpen();
+          return this;
+        },
+        /**
+         * Closes the transport.
+         */
+      },
+      {
+        key: "close",
+        value: function close() {
+          if (this.readyState === "opening" || this.readyState === "open") {
+            this.doClose();
+            this.onClose();
+          }
+          return this;
+        },
+        /**
+         * Sends multiple packets.
+         *
+         * @param {Array} packets
+         */
+      },
+      {
+        key: "send",
+        value: function send(packets) {
+          if (this.readyState === "open") {
+            this.write(packets);
+          }
+        },
+        /**
+         * Called upon open
+         *
+         * @protected
+         */
+      },
+      {
+        key: "onOpen",
+        value: function onOpen() {
+          this.readyState = "open";
+          this.writable = true;
+          _get(_getPrototypeOf(Transport.prototype), "emitReserved", this).call(
+            this,
+            "open",
+          );
+        },
+        /**
+         * Called with data.
+         *
+         * @param {String} data
+         * @protected
+         */
+      },
+      {
+        key: "onData",
+        value: function onData(data) {
+          var packet = decodePacket(data, this.socket.binaryType);
+          this.onPacket(packet);
+        },
+        /**
+         * Called with a decoded packet.
+         *
+         * @protected
+         */
+      },
+      {
+        key: "onPacket",
+        value: function onPacket(packet) {
+          _get(_getPrototypeOf(Transport.prototype), "emitReserved", this).call(
+            this,
+            "packet",
+            packet,
+          );
+        },
+        /**
+         * Called upon close.
+         *
+         * @protected
+         */
+      },
+      {
+        key: "onClose",
+        value: function onClose(details) {
+          this.readyState = "closed";
+          _get(_getPrototypeOf(Transport.prototype), "emitReserved", this).call(
+            this,
+            "close",
+            details,
+          );
+        },
+        /**
+         * Pauses the transport, in order not to lose packets during an upgrade.
+         *
+         * @param onPause
+         */
+      },
+      {
+        key: "pause",
+        value: function pause(onPause) {},
+      },
+      {
+        key: "createUri",
+        value: function createUri(schema) {
+          var query =
+            arguments.length > 1 && arguments[1] !== undefined
+              ? arguments[1]
+              : {};
+          return (
+            schema +
+            "://" +
+            this._hostname() +
+            this._port() +
+            this.opts.path +
+            this._query(query)
+          );
+        },
+      },
+      {
+        key: "_hostname",
+        value: function _hostname() {
+          var hostname = this.opts.hostname;
+          return hostname.indexOf(":") === -1 ? hostname : "[" + hostname + "]";
+        },
+      },
+      {
+        key: "_port",
+        value: function _port() {
+          if (
+            this.opts.port &&
+            ((this.opts.secure && Number(this.opts.port !== 443)) ||
+              (!this.opts.secure && Number(this.opts.port) !== 80))
+          ) {
+            return ":" + this.opts.port;
+          } else {
+            return "";
+          }
+        },
+      },
+      {
+        key: "_query",
+        value: function _query(query) {
+          var encodedQuery = encode$1(query);
+          return encodedQuery.length ? "?" + encodedQuery : "";
+        },
+      },
+    ]);
+    return Transport;
+  })(Emitter);
+
+  // imported from https://github.com/unshiftio/yeast
+
+  var alphabet =
+      "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".split(
+        "",
+      ),
+    length = 64,
+    map = {};
+  var seed = 0,
+    i = 0,
+    prev;
+  /**
+   * Return a string representing the specified number.
+   *
+   * @param {Number} num The number to convert.
+   * @returns {String} The string representation of the number.
+   * @api public
+   */
+  function encode(num) {
+    var encoded = "";
+    do {
+      encoded = alphabet[num % length] + encoded;
+      num = Math.floor(num / length);
+    } while (num > 0);
+    return encoded;
+  }
+  /**
+   * Yeast: A tiny growing id generator.
+   *
+   * @returns {String} A unique id.
+   * @api public
+   */
+  function yeast() {
+    var now = encode(+new Date());
+    if (now !== prev) return (seed = 0), (prev = now);
+    return now + "." + encode(seed++);
+  }
+  //
+  // Map each character to its index.
+  //
+  for (; i < length; i++) map[alphabet[i]] = i;
+
+  // imported from https://github.com/component/has-cors
+  var value = false;
+  try {
+    value =
+      typeof XMLHttpRequest !== "undefined" &&
+      "withCredentials" in new XMLHttpRequest();
+  } catch (err) {
+    // if XMLHttp support is disabled in IE then it will throw
+    // when trying to create
+  }
+  var hasCORS = value;
+
+  // browser shim for xmlhttprequest module
+  function XHR(opts) {
+    var xdomain = opts.xdomain;
+    // XMLHttpRequest can be disabled on IE
+    try {
+      if ("undefined" !== typeof XMLHttpRequest && (!xdomain || hasCORS)) {
+        return new XMLHttpRequest();
+      }
+    } catch (e) {}
+    if (!xdomain) {
+      try {
+        return new globalThisShim[["Active"].concat("Object").join("X")](
+          "Microsoft.XMLHTTP",
+        );
+      } catch (e) {}
+    }
+  }
+  function createCookieJar() {}
+
+  function empty() {}
+  var hasXHR2 = (function () {
+    var xhr = new XHR({
+      xdomain: false,
+    });
+    return null != xhr.responseType;
+  })();
+  var Polling = /*#__PURE__*/ (function (_Transport) {
+    _inherits(Polling, _Transport);
+    var _super = _createSuper(Polling);
+    /**
+     * XHR Polling constructor.
+     *
+     * @param {Object} opts
+     * @package
+     */
+    function Polling(opts) {
+      var _this;
+      _classCallCheck(this, Polling);
+      _this = _super.call(this, opts);
+      _this.polling = false;
+      if (typeof location !== "undefined") {
+        var isSSL = "https:" === location.protocol;
+        var port = location.port;
+        // some user agents have empty `location.port`
+        if (!port) {
+          port = isSSL ? "443" : "80";
+        }
+        _this.xd =
+          (typeof location !== "undefined" &&
+            opts.hostname !== location.hostname) ||
+          port !== opts.port;
+      }
+      /**
+       * XHR supports binary
+       */
+      var forceBase64 = opts && opts.forceBase64;
+      _this.supportsBinary = hasXHR2 && !forceBase64;
+      if (_this.opts.withCredentials) {
+        _this.cookieJar = createCookieJar();
+      }
+      return _this;
+    }
+    _createClass(Polling, [
+      {
+        key: "name",
+        get: function get() {
+          return "polling";
+        },
+        /**
+         * Opens the socket (triggers polling). We write a PING message to determine
+         * when the transport is open.
+         *
+         * @protected
+         */
+      },
+      {
+        key: "doOpen",
+        value: function doOpen() {
+          this.poll();
+        },
+        /**
+         * Pauses polling.
+         *
+         * @param {Function} onPause - callback upon buffers are flushed and transport is paused
+         * @package
+         */
+      },
+      {
+        key: "pause",
+        value: function pause(onPause) {
+          var _this2 = this;
+          this.readyState = "pausing";
+          var pause = function pause() {
+            _this2.readyState = "paused";
+            onPause();
+          };
+          if (this.polling || !this.writable) {
+            var total = 0;
+            if (this.polling) {
+              total++;
+              this.once("pollComplete", function () {
+                --total || pause();
+              });
+            }
+            if (!this.writable) {
+              total++;
+              this.once("drain", function () {
+                --total || pause();
+              });
+            }
+          } else {
+            pause();
+          }
+        },
+        /**
+         * Starts polling cycle.
+         *
+         * @private
+         */
+      },
+      {
+        key: "poll",
+        value: function poll() {
+          this.polling = true;
+          this.doPoll();
+          this.emitReserved("poll");
+        },
+        /**
+         * Overloads onData to detect payloads.
+         *
+         * @protected
+         */
+      },
+      {
+        key: "onData",
+        value: function onData(data) {
+          var _this3 = this;
+          var callback = function callback(packet) {
+            // if its the first message we consider the transport open
+            if ("opening" === _this3.readyState && packet.type === "open") {
+              _this3.onOpen();
+            }
+            // if its a close packet, we close the ongoing requests
+            if ("close" === packet.type) {
+              _this3.onClose({
+                description: "transport closed by the server",
+              });
+              return false;
+            }
+            // otherwise bypass onData and handle the message
+            _this3.onPacket(packet);
+          };
+          // decode payload
+          decodePayload(data, this.socket.binaryType).forEach(callback);
+          // if an event did not trigger closing
+          if ("closed" !== this.readyState) {
+            // if we got data we're not polling
+            this.polling = false;
+            this.emitReserved("pollComplete");
+            if ("open" === this.readyState) {
+              this.poll();
+            }
+          }
+        },
+        /**
+         * For polling, send a close packet.
+         *
+         * @protected
+         */
+      },
+      {
+        key: "doClose",
+        value: function doClose() {
+          var _this4 = this;
+          var close = function close() {
+            _this4.write([
+              {
+                type: "close",
+              },
+            ]);
+          };
+          if ("open" === this.readyState) {
+            close();
+          } else {
+            // in case we're trying to close while
+            // handshaking is in progress (GH-164)
+            this.once("open", close);
+          }
+        },
+        /**
+         * Writes a packets payload.
+         *
+         * @param {Array} packets - data packets
+         * @protected
+         */
+      },
+      {
+        key: "write",
+        value: function write(packets) {
+          var _this5 = this;
+          this.writable = false;
+          encodePayload(packets, function (data) {
+            _this5.doWrite(data, function () {
+              _this5.writable = true;
+              _this5.emitReserved("drain");
+            });
+          });
+        },
+        /**
+         * Generates uri for connection.
+         *
+         * @private
+         */
+      },
+      {
+        key: "uri",
+        value: function uri() {
+          var schema = this.opts.secure ? "https" : "http";
+          var query = this.query || {};
+          // cache busting is forced
+          if (false !== this.opts.timestampRequests) {
+            query[this.opts.timestampParam] = yeast();
+          }
+          if (!this.supportsBinary && !query.sid) {
+            query.b64 = 1;
+          }
+          return this.createUri(schema, query);
+        },
+        /**
+         * Creates a request.
+         *
+         * @param {String} method
+         * @private
+         */
+      },
+      {
+        key: "request",
+        value: function request() {
+          var opts =
+            arguments.length > 0 && arguments[0] !== undefined
+              ? arguments[0]
+              : {};
+          _extends(
+            opts,
+            {
+              xd: this.xd,
+              cookieJar: this.cookieJar,
+            },
+            this.opts,
+          );
+          return new Request(this.uri(), opts);
+        },
+        /**
+         * Sends data.
+         *
+         * @param {String} data to send.
+         * @param {Function} called upon flush.
+         * @private
+         */
+      },
+      {
+        key: "doWrite",
+        value: function doWrite(data, fn) {
+          var _this6 = this;
+          var req = this.request({
+            method: "POST",
+            data: data,
+          });
+          req.on("success", fn);
+          req.on("error", function (xhrStatus, context) {
+            _this6.onError("xhr post error", xhrStatus, context);
+          });
+        },
+        /**
+         * Starts a poll cycle.
+         *
+         * @private
+         */
+      },
+      {
+        key: "doPoll",
+        value: function doPoll() {
+          var _this7 = this;
+          var req = this.request();
+          req.on("data", this.onData.bind(this));
+          req.on("error", function (xhrStatus, context) {
+            _this7.onError("xhr poll error", xhrStatus, context);
+          });
+          this.pollXhr = req;
+        },
+      },
+    ]);
+    return Polling;
+  })(Transport);
+  var Request = /*#__PURE__*/ (function (_Emitter) {
+    _inherits(Request, _Emitter);
+    var _super2 = _createSuper(Request);
+    /**
+     * Request constructor
+     *
+     * @param {Object} options
+     * @package
+     */
+    function Request(uri, opts) {
+      var _this8;
+      _classCallCheck(this, Request);
+      _this8 = _super2.call(this);
+      installTimerFunctions(_assertThisInitialized(_this8), opts);
+      _this8.opts = opts;
+      _this8.method = opts.method || "GET";
+      _this8.uri = uri;
+      _this8.data = undefined !== opts.data ? opts.data : null;
+      _this8.create();
+      return _this8;
+    }
+    /**
+     * Creates the XHR object and sends the request.
+     *
+     * @private
+     */
+    _createClass(Request, [
+      {
+        key: "create",
+        value: function create() {
+          var _this9 = this;
+          var _a;
+          var opts = pick(
+            this.opts,
+            "agent",
+            "pfx",
+            "key",
+            "passphrase",
+            "cert",
+            "ca",
+            "ciphers",
+            "rejectUnauthorized",
+            "autoUnref",
+          );
+          opts.xdomain = !!this.opts.xd;
+          var xhr = (this.xhr = new XHR(opts));
+          try {
+            xhr.open(this.method, this.uri, true);
+            try {
+              if (this.opts.extraHeaders) {
+                xhr.setDisableHeaderCheck && xhr.setDisableHeaderCheck(true);
+                for (var i in this.opts.extraHeaders) {
+                  if (this.opts.extraHeaders.hasOwnProperty(i)) {
+                    xhr.setRequestHeader(i, this.opts.extraHeaders[i]);
+                  }
+                }
+              }
+            } catch (e) {}
+            if ("POST" === this.method) {
+              try {
+                xhr.setRequestHeader(
+                  "Content-type",
+                  "text/plain;charset=UTF-8",
+                );
+              } catch (e) {}
+            }
+            try {
+              xhr.setRequestHeader("Accept", "*/*");
+            } catch (e) {}
+            (_a = this.opts.cookieJar) === null || _a === void 0
+              ? void 0
+              : _a.addCookies(xhr);
+            // ie6 check
+            if ("withCredentials" in xhr) {
+              xhr.withCredentials = this.opts.withCredentials;
+            }
+            if (this.opts.requestTimeout) {
+              xhr.timeout = this.opts.requestTimeout;
+            }
+            xhr.onreadystatechange = function () {
+              var _a;
+              if (xhr.readyState === 3) {
+                (_a = _this9.opts.cookieJar) === null || _a === void 0
+                  ? void 0
+                  : _a.parseCookies(xhr);
+              }
+              if (4 !== xhr.readyState) return;
+              if (200 === xhr.status || 1223 === xhr.status) {
+                _this9.onLoad();
+              } else {
+                // make sure the `error` event handler that's user-set
+                // does not throw in the same tick and gets caught here
+                _this9.setTimeoutFn(function () {
+                  _this9.onError(
+                    typeof xhr.status === "number" ? xhr.status : 0,
+                  );
+                }, 0);
+              }
+            };
+            xhr.send(this.data);
+          } catch (e) {
+            // Need to defer since .create() is called directly from the constructor
+            // and thus the 'error' event can only be only bound *after* this exception
+            // occurs.  Therefore, also, we cannot throw here at all.
+            this.setTimeoutFn(function () {
+              _this9.onError(e);
+            }, 0);
+            return;
+          }
+          if (typeof document !== "undefined") {
+            this.index = Request.requestsCount++;
+            Request.requests[this.index] = this;
+          }
+        },
+        /**
+         * Called upon error.
+         *
+         * @private
+         */
+      },
+      {
+        key: "onError",
+        value: function onError(err) {
+          this.emitReserved("error", err, this.xhr);
+          this.cleanup(true);
+        },
+        /**
+         * Cleans up house.
+         *
+         * @private
+         */
+      },
+      {
+        key: "cleanup",
+        value: function cleanup(fromError) {
+          if ("undefined" === typeof this.xhr || null === this.xhr) {
+            return;
+          }
+          this.xhr.onreadystatechange = empty;
+          if (fromError) {
+            try {
+              this.xhr.abort();
+            } catch (e) {}
+          }
+          if (typeof document !== "undefined") {
+            delete Request.requests[this.index];
+          }
+          this.xhr = null;
+        },
+        /**
+         * Called upon load.
+         *
+         * @private
+         */
+      },
+      {
+        key: "onLoad",
+        value: function onLoad() {
+          var data = this.xhr.responseText;
+          if (data !== null) {
+            this.emitReserved("data", data);
+            this.emitReserved("success");
+            this.cleanup();
+          }
+        },
+        /**
+         * Aborts the request.
+         *
+         * @package
+         */
+      },
+      {
+        key: "abort",
+        value: function abort() {
+          this.cleanup();
+        },
+      },
+    ]);
+    return Request;
+  })(Emitter);
+  Request.requestsCount = 0;
+  Request.requests = {};
+  /**
+   * Aborts pending requests when unloading the window. This is needed to prevent
+   * memory leaks (e.g. when using IE) and to ensure that no spurious error is
+   * emitted.
+   */
+  if (typeof document !== "undefined") {
+    // @ts-ignore
+    if (typeof attachEvent === "function") {
+      // @ts-ignore
+      attachEvent("onunload", unloadHandler);
+    } else if (typeof addEventListener === "function") {
+      var terminationEvent =
+        "onpagehide" in globalThisShim ? "pagehide" : "unload";
+      addEventListener(terminationEvent, unloadHandler, false);
+    }
+  }
+  function unloadHandler() {
+    for (var i in Request.requests) {
+      if (Request.requests.hasOwnProperty(i)) {
+        Request.requests[i].abort();
+      }
+    }
+  }
+
+  var nextTick = (function () {
+    var isPromiseAvailable =
+      typeof Promise === "function" && typeof Promise.resolve === "function";
+    if (isPromiseAvailable) {
+      return function (cb) {
+        return Promise.resolve().then(cb);
+      };
+    } else {
+      return function (cb, setTimeoutFn) {
+        return setTimeoutFn(cb, 0);
+      };
+    }
+  })();
+  var WebSocket = globalThisShim.WebSocket || globalThisShim.MozWebSocket;
+  var usingBrowserWebSocket = true;
+  var defaultBinaryType = "arraybuffer";
+
+  // detect ReactNative environment
+  var isReactNative =
+    typeof navigator !== "undefined" &&
+    typeof navigator.product === "string" &&
+    navigator.product.toLowerCase() === "reactnative";
+  var WS = /*#__PURE__*/ (function (_Transport) {
+    _inherits(WS, _Transport);
+    var _super = _createSuper(WS);
+    /**
+     * WebSocket transport constructor.
+     *
+     * @param {Object} opts - connection options
+     * @protected
+     */
+    function WS(opts) {
+      var _this;
+      _classCallCheck(this, WS);
+      _this = _super.call(this, opts);
+      _this.supportsBinary = !opts.forceBase64;
+      return _this;
+    }
+    _createClass(WS, [
+      {
+        key: "name",
+        get: function get() {
+          return "websocket";
+        },
+      },
+      {
+        key: "doOpen",
+        value: function doOpen() {
+          if (!this.check()) {
+            // let probe timeout
+            return;
+          }
+          var uri = this.uri();
+          var protocols = this.opts.protocols;
+          // React Native only supports the 'headers' option, and will print a warning if anything else is passed
+          var opts = isReactNative
+            ? {}
+            : pick(
+                this.opts,
+                "agent",
+                "perMessageDeflate",
+                "pfx",
+                "key",
+                "passphrase",
+                "cert",
+                "ca",
+                "ciphers",
+                "rejectUnauthorized",
+                "localAddress",
+                "protocolVersion",
+                "origin",
+                "maxPayload",
+                "family",
+                "checkServerIdentity",
+              );
+          if (this.opts.extraHeaders) {
+            opts.headers = this.opts.extraHeaders;
+          }
+          try {
+            this.ws =
+              usingBrowserWebSocket && !isReactNative
+                ? protocols
+                  ? new WebSocket(uri, protocols)
+                  : new WebSocket(uri)
+                : new WebSocket(uri, protocols, opts);
+          } catch (err) {
+            return this.emitReserved("error", err);
+          }
+          this.ws.binaryType = this.socket.binaryType;
+          this.addEventListeners();
+        },
+        /**
+         * Adds event listeners to the socket
+         *
+         * @private
+         */
+      },
+      {
+        key: "addEventListeners",
+        value: function addEventListeners() {
+          var _this2 = this;
+          this.ws.onopen = function () {
+            if (_this2.opts.autoUnref) {
+              _this2.ws._socket.unref();
+            }
+            _this2.onOpen();
+          };
+          this.ws.onclose = function (closeEvent) {
+            return _this2.onClose({
+              description: "websocket connection closed",
+              context: closeEvent,
+            });
+          };
+          this.ws.onmessage = function (ev) {
+            return _this2.onData(ev.data);
+          };
+          this.ws.onerror = function (e) {
+            return _this2.onError("websocket error", e);
+          };
+        },
+      },
+      {
+        key: "write",
+        value: function write(packets) {
+          var _this3 = this;
+          this.writable = false;
+          // encodePacket efficient as it uses WS framing
+          // no need for encodePayload
+          var _loop = function _loop() {
+            var packet = packets[i];
+            var lastPacket = i === packets.length - 1;
+            encodePacket(packet, _this3.supportsBinary, function (data) {
+              // always create a new object (GH-437)
+              var opts = {};
+              // Sometimes the websocket has already been closed but the browser didn't
+              // have a chance of informing us about it yet, in that case send will
+              // throw an error
+              try {
+                if (usingBrowserWebSocket) {
+                  // TypeError is thrown when passing the second argument on Safari
+                  _this3.ws.send(data);
+                }
+              } catch (e) {}
+              if (lastPacket) {
+                // fake drain
+                // defer to next tick to allow Socket to clear writeBuffer
+                nextTick(function () {
+                  _this3.writable = true;
+                  _this3.emitReserved("drain");
+                }, _this3.setTimeoutFn);
+              }
+            });
+          };
+          for (var i = 0; i < packets.length; i++) {
+            _loop();
+          }
+        },
+      },
+      {
+        key: "doClose",
+        value: function doClose() {
+          if (typeof this.ws !== "undefined") {
+            this.ws.close();
+            this.ws = null;
+          }
+        },
+        /**
+         * Generates uri for connection.
+         *
+         * @private
+         */
+      },
+      {
+        key: "uri",
+        value: function uri() {
+          var schema = this.opts.secure ? "wss" : "ws";
+          var query = this.query || {};
+          // append timestamp to URI
+          if (this.opts.timestampRequests) {
+            query[this.opts.timestampParam] = yeast();
+          }
+          // communicate binary support capabilities
+          if (!this.supportsBinary) {
+            query.b64 = 1;
+          }
+          return this.createUri(schema, query);
+        },
+        /**
+         * Feature detection for WebSocket.
+         *
+         * @return {Boolean} whether this transport is available.
+         * @private
+         */
+      },
+      {
+        key: "check",
+        value: function check() {
+          return !!WebSocket;
+        },
+      },
+    ]);
+    return WS;
+  })(Transport);
+
+  var WT = /*#__PURE__*/ (function (_Transport) {
+    _inherits(WT, _Transport);
+    var _super = _createSuper(WT);
+    function WT() {
+      _classCallCheck(this, WT);
+      return _super.apply(this, arguments);
+    }
+    _createClass(WT, [
+      {
+        key: "name",
+        get: function get() {
+          return "webtransport";
+        },
+      },
+      {
+        key: "doOpen",
+        value: function doOpen() {
+          var _this = this;
+          // @ts-ignore
+          if (typeof WebTransport !== "function") {
+            return;
+          }
+          // @ts-ignore
+          this.transport = new WebTransport(
+            this.createUri("https"),
+            this.opts.transportOptions[this.name],
+          );
+          this.transport.closed
+            .then(function () {
+              _this.onClose();
+            })
+            ["catch"](function (err) {
+              _this.onError("webtransport error", err);
+            });
+          // note: we could have used async/await, but that would require some additional polyfills
+          this.transport.ready.then(function () {
+            _this.transport.createBidirectionalStream().then(function (stream) {
+              var decoderStream = createPacketDecoderStream(
+                Number.MAX_SAFE_INTEGER,
+                _this.socket.binaryType,
+              );
+              var reader = stream.readable
+                .pipeThrough(decoderStream)
+                .getReader();
+              var encoderStream = createPacketEncoderStream();
+              encoderStream.readable.pipeTo(stream.writable);
+              _this.writer = encoderStream.writable.getWriter();
+              var read = function read() {
+                reader
+                  .read()
+                  .then(function (_ref) {
+                    var done = _ref.done,
+                      value = _ref.value;
+                    if (done) {
+                      return;
+                    }
+                    _this.onPacket(value);
+                    read();
+                  })
+                  ["catch"](function (err) {});
+              };
+              read();
+              var packet = {
+                type: "open",
+              };
+              if (_this.query.sid) {
+                packet.data = '{"sid":"'.concat(_this.query.sid, '"}');
+              }
+              _this.writer.write(packet).then(function () {
+                return _this.onOpen();
+              });
+            });
+          });
+        },
+      },
+      {
+        key: "write",
+        value: function write(packets) {
+          var _this2 = this;
+          this.writable = false;
+          var _loop = function _loop() {
+            var packet = packets[i];
+            var lastPacket = i === packets.length - 1;
+            _this2.writer.write(packet).then(function () {
+              if (lastPacket) {
+                nextTick(function () {
+                  _this2.writable = true;
+                  _this2.emitReserved("drain");
+                }, _this2.setTimeoutFn);
+              }
+            });
+          };
+          for (var i = 0; i < packets.length; i++) {
+            _loop();
+          }
+        },
+      },
+      {
+        key: "doClose",
+        value: function doClose() {
+          var _a;
+          (_a = this.transport) === null || _a === void 0 ? void 0 : _a.close();
+        },
+      },
+    ]);
+    return WT;
+  })(Transport);
+
+  var transports = {
+    websocket: WS,
+    webtransport: WT,
+    polling: Polling,
+  };
+
+  // imported from https://github.com/galkn/parseuri
+  /**
+   * Parses a URI
+   *
+   * Note: we could also have used the built-in URL object, but it isn't supported on all platforms.
+   *
+   * See:
+   * - https://developer.mozilla.org/en-US/docs/Web/API/URL
+   * - https://caniuse.com/url
+   * - https://www.rfc-editor.org/rfc/rfc3986#appendix-B
+   *
+   * History of the parse() method:
+   * - first commit: https://github.com/socketio/socket.io-client/commit/4ee1d5d94b3906a9c052b459f1a818b15f38f91c
+   * - export into its own module: https://github.com/socketio/engine.io-client/commit/de2c561e4564efeb78f1bdb1ba39ef81b2822cb3
+   * - reimport: https://github.com/socketio/engine.io-client/commit/df32277c3f6d622eec5ed09f493cae3f3391d242
+   *
+   * @author Steven Levithan <stevenlevithan.com> (MIT license)
+   * @api private
+   */
+  var re =
+    /^(?:(?![^:@\/?#]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@\/?#]*)(?::([^:@\/?#]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
+  var parts = [
+    "source",
+    "protocol",
+    "authority",
+    "userInfo",
+    "user",
+    "password",
+    "host",
+    "port",
+    "relative",
+    "path",
+    "directory",
+    "file",
+    "query",
+    "anchor",
+  ];
+  function parse(str) {
+    var src = str,
+      b = str.indexOf("["),
+      e = str.indexOf("]");
+    if (b != -1 && e != -1) {
+      str =
+        str.substring(0, b) +
+        str.substring(b, e).replace(/:/g, ";") +
+        str.substring(e, str.length);
+    }
+    var m = re.exec(str || ""),
+      uri = {},
+      i = 14;
+    while (i--) {
+      uri[parts[i]] = m[i] || "";
+    }
+    if (b != -1 && e != -1) {
+      uri.source = src;
+      uri.host = uri.host.substring(1, uri.host.length - 1).replace(/;/g, ":");
+      uri.authority = uri.authority
+        .replace("[", "")
+        .replace("]", "")
+        .replace(/;/g, ":");
+      uri.ipv6uri = true;
+    }
+    uri.pathNames = pathNames(uri, uri["path"]);
+    uri.queryKey = queryKey(uri, uri["query"]);
+    return uri;
+  }
+  function pathNames(obj, path) {
+    var regx = /\/{2,9}/g,
+      names = path.replace(regx, "/").split("/");
+    if (path.slice(0, 1) == "/" || path.length === 0) {
+      names.splice(0, 1);
+    }
+    if (path.slice(-1) == "/") {
+      names.splice(names.length - 1, 1);
+    }
+    return names;
+  }
+  function queryKey(uri, query) {
+    var data = {};
+    query.replace(/(?:^|&)([^&=]*)=?([^&]*)/g, function ($0, $1, $2) {
+      if ($1) {
+        data[$1] = $2;
+      }
+    });
+    return data;
+  }
+
+  var Socket$1 = /*#__PURE__*/ (function (_Emitter) {
+    _inherits(Socket, _Emitter);
+    var _super = _createSuper(Socket);
+    /**
+     * Socket constructor.
+     *
+     * @param {String|Object} uri - uri or options
+     * @param {Object} opts - options
+     */
+    function Socket(uri) {
+      var _this;
+      var opts =
+        arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+      _classCallCheck(this, Socket);
+      _this = _super.call(this);
+      _this.binaryType = defaultBinaryType;
+      _this.writeBuffer = [];
+      if (uri && "object" === _typeof(uri)) {
+        opts = uri;
+        uri = null;
+      }
+      if (uri) {
+        uri = parse(uri);
+        opts.hostname = uri.host;
+        opts.secure = uri.protocol === "https" || uri.protocol === "wss";
+        opts.port = uri.port;
+        if (uri.query) opts.query = uri.query;
+      } else if (opts.host) {
+        opts.hostname = parse(opts.host).host;
+      }
+      installTimerFunctions(_assertThisInitialized(_this), opts);
+      _this.secure =
+        null != opts.secure
+          ? opts.secure
+          : typeof location !== "undefined" && "https:" === location.protocol;
+      if (opts.hostname && !opts.port) {
+        // if no port is specified manually, use the protocol default
+        opts.port = _this.secure ? "443" : "80";
+      }
+      _this.hostname =
+        opts.hostname ||
+        (typeof location !== "undefined" ? location.hostname : "localhost");
+      _this.port =
+        opts.port ||
+        (typeof location !== "undefined" && location.port
+          ? location.port
+          : _this.secure
+            ? "443"
+            : "80");
+      _this.transports = opts.transports || [
+        "polling",
+        "websocket",
+        "webtransport",
+      ];
+      _this.writeBuffer = [];
+      _this.prevBufferLen = 0;
+      _this.opts = _extends(
+        {
+          path: "/engine.io",
+          agent: false,
+          withCredentials: false,
+          upgrade: true,
+          timestampParam: "t",
+          rememberUpgrade: false,
+          addTrailingSlash: true,
+          rejectUnauthorized: true,
+          perMessageDeflate: {
+            threshold: 1024,
+          },
+          transportOptions: {},
+          closeOnBeforeunload: false,
+        },
+        opts,
+      );
+      _this.opts.path =
+        _this.opts.path.replace(/\/$/, "") +
+        (_this.opts.addTrailingSlash ? "/" : "");
+      if (typeof _this.opts.query === "string") {
+        _this.opts.query = decode(_this.opts.query);
+      }
+      // set on handshake
+      _this.id = null;
+      _this.upgrades = null;
+      _this.pingInterval = null;
+      _this.pingTimeout = null;
+      // set on heartbeat
+      _this.pingTimeoutTimer = null;
+      if (typeof addEventListener === "function") {
+        if (_this.opts.closeOnBeforeunload) {
+          // Firefox closes the connection when the "beforeunload" event is emitted but not Chrome. This event listener
+          // ensures every browser behaves the same (no "disconnect" event at the Socket.IO level when the page is
+          // closed/reloaded)
+          _this.beforeunloadEventListener = function () {
+            if (_this.transport) {
+              // silently close the transport
+              _this.transport.removeAllListeners();
+              _this.transport.close();
+            }
+          };
+          addEventListener(
+            "beforeunload",
+            _this.beforeunloadEventListener,
+            false,
+          );
+        }
+        if (_this.hostname !== "localhost") {
+          _this.offlineEventListener = function () {
+            _this.onClose("transport close", {
+              description: "network connection lost",
+            });
+          };
+          addEventListener("offline", _this.offlineEventListener, false);
+        }
+      }
+      _this.open();
+      return _this;
+    }
+    /**
+     * Creates transport of the given type.
+     *
+     * @param {String} name - transport name
+     * @return {Transport}
+     * @private
+     */
+    _createClass(Socket, [
+      {
+        key: "createTransport",
+        value: function createTransport(name) {
+          var query = _extends({}, this.opts.query);
+          // append engine.io protocol identifier
+          query.EIO = protocol$1;
+          // transport name
+          query.transport = name;
+          // session id if we already have one
+          if (this.id) query.sid = this.id;
+          var opts = _extends(
+            {},
+            this.opts,
+            {
+              query: query,
+              socket: this,
+              hostname: this.hostname,
+              secure: this.secure,
+              port: this.port,
+            },
+            this.opts.transportOptions[name],
+          );
+          return new transports[name](opts);
+        },
+        /**
+         * Initializes transport to use and starts probe.
+         *
+         * @private
+         */
+      },
+      {
+        key: "open",
+        value: function open() {
+          var _this2 = this;
+          var transport;
+          if (
+            this.opts.rememberUpgrade &&
+            Socket.priorWebsocketSuccess &&
+            this.transports.indexOf("websocket") !== -1
+          ) {
+            transport = "websocket";
+          } else if (0 === this.transports.length) {
+            // Emit error on next tick so it can be listened to
+            this.setTimeoutFn(function () {
+              _this2.emitReserved("error", "No transports available");
+            }, 0);
+            return;
+          } else {
+            transport = this.transports[0];
+          }
+          this.readyState = "opening";
+          // Retry with the next transport if the transport is disabled (jsonp: false)
+          try {
+            transport = this.createTransport(transport);
+          } catch (e) {
+            this.transports.shift();
+            this.open();
+            return;
+          }
+          transport.open();
+          this.setTransport(transport);
+        },
+        /**
+         * Sets the current transport. Disables the existing one (if any).
+         *
+         * @private
+         */
+      },
+      {
+        key: "setTransport",
+        value: function setTransport(transport) {
+          var _this3 = this;
+          if (this.transport) {
+            this.transport.removeAllListeners();
+          }
+          // set up transport
+          this.transport = transport;
+          // set up transport listeners
+          transport
+            .on("drain", this.onDrain.bind(this))
+            .on("packet", this.onPacket.bind(this))
+            .on("error", this.onError.bind(this))
+            .on("close", function (reason) {
+              return _this3.onClose("transport close", reason);
+            });
+        },
+        /**
+         * Probes a transport.
+         *
+         * @param {String} name - transport name
+         * @private
+         */
+      },
+      {
+        key: "probe",
+        value: function probe(name) {
+          var _this4 = this;
+          var transport = this.createTransport(name);
+          var failed = false;
+          Socket.priorWebsocketSuccess = false;
+          var onTransportOpen = function onTransportOpen() {
+            if (failed) return;
+            transport.send([
+              {
+                type: "ping",
+                data: "probe",
+              },
+            ]);
+            transport.once("packet", function (msg) {
+              if (failed) return;
+              if ("pong" === msg.type && "probe" === msg.data) {
+                _this4.upgrading = true;
+                _this4.emitReserved("upgrading", transport);
+                if (!transport) return;
+                Socket.priorWebsocketSuccess = "websocket" === transport.name;
+                _this4.transport.pause(function () {
+                  if (failed) return;
+                  if ("closed" === _this4.readyState) return;
+                  cleanup();
+                  _this4.setTransport(transport);
+                  transport.send([
+                    {
+                      type: "upgrade",
+                    },
+                  ]);
+                  _this4.emitReserved("upgrade", transport);
+                  transport = null;
+                  _this4.upgrading = false;
+                  _this4.flush();
+                });
+              } else {
+                var err = new Error("probe error");
+                // @ts-ignore
+                err.transport = transport.name;
+                _this4.emitReserved("upgradeError", err);
+              }
+            });
+          };
+          function freezeTransport() {
+            if (failed) return;
+            // Any callback called by transport should be ignored since now
+            failed = true;
+            cleanup();
+            transport.close();
+            transport = null;
+          }
+          // Handle any error that happens while probing
+          var onerror = function onerror(err) {
+            var error = new Error("probe error: " + err);
+            // @ts-ignore
+            error.transport = transport.name;
+            freezeTransport();
+            _this4.emitReserved("upgradeError", error);
+          };
+          function onTransportClose() {
+            onerror("transport closed");
+          }
+          // When the socket is closed while we're probing
+          function onclose() {
+            onerror("socket closed");
+          }
+          // When the socket is upgraded while we're probing
+          function onupgrade(to) {
+            if (transport && to.name !== transport.name) {
+              freezeTransport();
+            }
+          }
+          // Remove all listeners on the transport and on self
+          var cleanup = function cleanup() {
+            transport.removeListener("open", onTransportOpen);
+            transport.removeListener("error", onerror);
+            transport.removeListener("close", onTransportClose);
+            _this4.off("close", onclose);
+            _this4.off("upgrading", onupgrade);
+          };
+          transport.once("open", onTransportOpen);
+          transport.once("error", onerror);
+          transport.once("close", onTransportClose);
+          this.once("close", onclose);
+          this.once("upgrading", onupgrade);
+          if (
+            this.upgrades.indexOf("webtransport") !== -1 &&
+            name !== "webtransport"
+          ) {
+            // favor WebTransport
+            this.setTimeoutFn(function () {
+              if (!failed) {
+                transport.open();
+              }
+            }, 200);
+          } else {
+            transport.open();
+          }
+        },
+        /**
+         * Called when connection is deemed open.
+         *
+         * @private
+         */
+      },
+      {
+        key: "onOpen",
+        value: function onOpen() {
+          this.readyState = "open";
+          Socket.priorWebsocketSuccess = "websocket" === this.transport.name;
+          this.emitReserved("open");
+          this.flush();
+          // we check for `readyState` in case an `open`
+          // listener already closed the socket
+          if ("open" === this.readyState && this.opts.upgrade) {
+            var i = 0;
+            var l = this.upgrades.length;
+            for (; i < l; i++) {
+              this.probe(this.upgrades[i]);
+            }
+          }
+        },
+        /**
+         * Handles a packet.
+         *
+         * @private
+         */
+      },
+      {
+        key: "onPacket",
+        value: function onPacket(packet) {
+          if (
+            "opening" === this.readyState ||
+            "open" === this.readyState ||
+            "closing" === this.readyState
+          ) {
+            this.emitReserved("packet", packet);
+            // Socket is live - any packet counts
+            this.emitReserved("heartbeat");
+            this.resetPingTimeout();
+            switch (packet.type) {
+              case "open":
+                this.onHandshake(JSON.parse(packet.data));
+                break;
+              case "ping":
+                this.sendPacket("pong");
+                this.emitReserved("ping");
+                this.emitReserved("pong");
+                break;
+              case "error":
+                var err = new Error("server error");
+                // @ts-ignore
+                err.code = packet.data;
+                this.onError(err);
+                break;
+              case "message":
+                this.emitReserved("data", packet.data);
+                this.emitReserved("message", packet.data);
+                break;
+            }
+          }
+        },
+        /**
+         * Called upon handshake completion.
+         *
+         * @param {Object} data - handshake obj
+         * @private
+         */
+      },
+      {
+        key: "onHandshake",
+        value: function onHandshake(data) {
+          this.emitReserved("handshake", data);
+          this.id = data.sid;
+          this.transport.query.sid = data.sid;
+          this.upgrades = this.filterUpgrades(data.upgrades);
+          this.pingInterval = data.pingInterval;
+          this.pingTimeout = data.pingTimeout;
+          this.maxPayload = data.maxPayload;
+          this.onOpen();
+          // In case open handler closes socket
+          if ("closed" === this.readyState) return;
+          this.resetPingTimeout();
+        },
+        /**
+         * Sets and resets ping timeout timer based on server pings.
+         *
+         * @private
+         */
+      },
+      {
+        key: "resetPingTimeout",
+        value: function resetPingTimeout() {
+          var _this5 = this;
+          this.clearTimeoutFn(this.pingTimeoutTimer);
+          this.pingTimeoutTimer = this.setTimeoutFn(function () {
+            _this5.onClose("ping timeout");
+          }, this.pingInterval + this.pingTimeout);
+          if (this.opts.autoUnref) {
+            this.pingTimeoutTimer.unref();
+          }
+        },
+        /**
+         * Called on `drain` event
+         *
+         * @private
+         */
+      },
+      {
+        key: "onDrain",
+        value: function onDrain() {
+          this.writeBuffer.splice(0, this.prevBufferLen);
+          // setting prevBufferLen = 0 is very important
+          // for example, when upgrading, upgrade packet is sent over,
+          // and a nonzero prevBufferLen could cause problems on `drain`
+          this.prevBufferLen = 0;
+          if (0 === this.writeBuffer.length) {
+            this.emitReserved("drain");
+          } else {
+            this.flush();
+          }
+        },
+        /**
+         * Flush write buffers.
+         *
+         * @private
+         */
+      },
+      {
+        key: "flush",
+        value: function flush() {
+          if (
+            "closed" !== this.readyState &&
+            this.transport.writable &&
+            !this.upgrading &&
+            this.writeBuffer.length
+          ) {
+            var packets = this.getWritablePackets();
+            this.transport.send(packets);
+            // keep track of current length of writeBuffer
+            // splice writeBuffer and callbackBuffer on `drain`
+            this.prevBufferLen = packets.length;
+            this.emitReserved("flush");
+          }
+        },
+        /**
+         * Ensure the encoded size of the writeBuffer is below the maxPayload value sent by the server (only for HTTP
+         * long-polling)
+         *
+         * @private
+         */
+      },
+      {
+        key: "getWritablePackets",
+        value: function getWritablePackets() {
+          var shouldCheckPayloadSize =
+            this.maxPayload &&
+            this.transport.name === "polling" &&
+            this.writeBuffer.length > 1;
+          if (!shouldCheckPayloadSize) {
+            return this.writeBuffer;
+          }
+          var payloadSize = 1; // first packet type
+          for (var i = 0; i < this.writeBuffer.length; i++) {
+            var data = this.writeBuffer[i].data;
+            if (data) {
+              payloadSize += byteLength(data);
+            }
+            if (i > 0 && payloadSize > this.maxPayload) {
+              return this.writeBuffer.slice(0, i);
+            }
+            payloadSize += 2; // separator + packet type
+          }
+
+          return this.writeBuffer;
+        },
+        /**
+         * Sends a message.
+         *
+         * @param {String} msg - message.
+         * @param {Object} options.
+         * @param {Function} callback function.
+         * @return {Socket} for chaining.
+         */
+      },
+      {
+        key: "write",
+        value: function write(msg, options, fn) {
+          this.sendPacket("message", msg, options, fn);
+          return this;
+        },
+      },
+      {
+        key: "send",
+        value: function send(msg, options, fn) {
+          this.sendPacket("message", msg, options, fn);
+          return this;
+        },
+        /**
+         * Sends a packet.
+         *
+         * @param {String} type: packet type.
+         * @param {String} data.
+         * @param {Object} options.
+         * @param {Function} fn - callback function.
+         * @private
+         */
+      },
+      {
+        key: "sendPacket",
+        value: function sendPacket(type, data, options, fn) {
+          if ("function" === typeof data) {
+            fn = data;
+            data = undefined;
+          }
+          if ("function" === typeof options) {
+            fn = options;
+            options = null;
+          }
+          if ("closing" === this.readyState || "closed" === this.readyState) {
+            return;
+          }
+          options = options || {};
+          options.compress = false !== options.compress;
+          var packet = {
+            type: type,
+            data: data,
+            options: options,
+          };
+          this.emitReserved("packetCreate", packet);
+          this.writeBuffer.push(packet);
+          if (fn) this.once("flush", fn);
+          this.flush();
+        },
+        /**
+         * Closes the connection.
+         */
+      },
+      {
+        key: "close",
+        value: function close() {
+          var _this6 = this;
+          var close = function close() {
+            _this6.onClose("forced close");
+            _this6.transport.close();
+          };
+          var cleanupAndClose = function cleanupAndClose() {
+            _this6.off("upgrade", cleanupAndClose);
+            _this6.off("upgradeError", cleanupAndClose);
+            close();
+          };
+          var waitForUpgrade = function waitForUpgrade() {
+            // wait for upgrade to finish since we can't send packets while pausing a transport
+            _this6.once("upgrade", cleanupAndClose);
+            _this6.once("upgradeError", cleanupAndClose);
+          };
+          if ("opening" === this.readyState || "open" === this.readyState) {
+            this.readyState = "closing";
+            if (this.writeBuffer.length) {
+              this.once("drain", function () {
+                if (_this6.upgrading) {
+                  waitForUpgrade();
+                } else {
+                  close();
+                }
+              });
+            } else if (this.upgrading) {
+              waitForUpgrade();
+            } else {
+              close();
+            }
+          }
+          return this;
+        },
+        /**
+         * Called upon transport error
+         *
+         * @private
+         */
+      },
+      {
+        key: "onError",
+        value: function onError(err) {
+          Socket.priorWebsocketSuccess = false;
+          this.emitReserved("error", err);
+          this.onClose("transport error", err);
+        },
+        /**
+         * Called upon transport close.
+         *
+         * @private
+         */
+      },
+      {
+        key: "onClose",
+        value: function onClose(reason, description) {
+          if (
+            "opening" === this.readyState ||
+            "open" === this.readyState ||
+            "closing" === this.readyState
+          ) {
+            // clear timers
+            this.clearTimeoutFn(this.pingTimeoutTimer);
+            // stop event from firing again for transport
+            this.transport.removeAllListeners("close");
+            // ensure transport won't stay open
+            this.transport.close();
+            // ignore further transport communication
+            this.transport.removeAllListeners();
+            if (typeof removeEventListener === "function") {
+              removeEventListener(
+                "beforeunload",
+                this.beforeunloadEventListener,
+                false,
+              );
+              removeEventListener("offline", this.offlineEventListener, false);
+            }
+            // set ready state
+            this.readyState = "closed";
+            // clear session id
+            this.id = null;
+            // emit close event
+            this.emitReserved("close", reason, description);
+            // clean buffers after, so users can still
+            // grab the buffers on `close` event
+            this.writeBuffer = [];
+            this.prevBufferLen = 0;
+          }
+        },
+        /**
+         * Filters upgrades, returning only those matching client transports.
+         *
+         * @param {Array} upgrades - server upgrades
+         * @private
+         */
+      },
+      {
+        key: "filterUpgrades",
+        value: function filterUpgrades(upgrades) {
+          var filteredUpgrades = [];
+          var i = 0;
+          var j = upgrades.length;
+          for (; i < j; i++) {
+            if (~this.transports.indexOf(upgrades[i]))
+              filteredUpgrades.push(upgrades[i]);
+          }
+          return filteredUpgrades;
+        },
+      },
+    ]);
+    return Socket;
+  })(Emitter);
+  Socket$1.protocol = protocol$1;
+
+  Socket$1.protocol;
+
+  /**
+   * URL parser.
+   *
+   * @param uri - url
+   * @param path - the request path of the connection
+   * @param loc - An object meant to mimic window.location.
+   *        Defaults to window.location.
+   * @public
+   */
+  function url(uri) {
+    var path =
+      arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
+    var loc = arguments.length > 2 ? arguments[2] : undefined;
+    var obj = uri;
+    // default to window.location
+    loc = loc || (typeof location !== "undefined" && location);
+    if (null == uri) uri = loc.protocol + "//" + loc.host;
+    // relative path support
+    if (typeof uri === "string") {
+      if ("/" === uri.charAt(0)) {
+        if ("/" === uri.charAt(1)) {
+          uri = loc.protocol + uri;
+        } else {
+          uri = loc.host + uri;
+        }
+      }
+      if (!/^(https?|wss?):\/\//.test(uri)) {
+        if ("undefined" !== typeof loc) {
+          uri = loc.protocol + "//" + uri;
+        } else {
+          uri = "https://" + uri;
+        }
+      }
+      // parse
+      obj = parse(uri);
+    }
+    // make sure we treat `localhost:80` and `localhost` equally
+    if (!obj.port) {
+      if (/^(http|ws)$/.test(obj.protocol)) {
+        obj.port = "80";
+      } else if (/^(http|ws)s$/.test(obj.protocol)) {
+        obj.port = "443";
+      }
+    }
+    obj.path = obj.path || "/";
+    var ipv6 = obj.host.indexOf(":") !== -1;
+    var host = ipv6 ? "[" + obj.host + "]" : obj.host;
+    // define unique id
+    obj.id = obj.protocol + "://" + host + ":" + obj.port + path;
+    // define href
+    obj.href =
+      obj.protocol +
+      "://" +
+      host +
+      (loc && loc.port === obj.port ? "" : ":" + obj.port);
+    return obj;
+  }
+
+  var withNativeArrayBuffer = typeof ArrayBuffer === "function";
+  var isView = function isView(obj) {
+    return typeof ArrayBuffer.isView === "function"
+      ? ArrayBuffer.isView(obj)
+      : obj.buffer instanceof ArrayBuffer;
+  };
+  var toString = Object.prototype.toString;
+  var withNativeBlob =
+    typeof Blob === "function" ||
+    (typeof Blob !== "undefined" &&
+      toString.call(Blob) === "[object BlobConstructor]");
+  var withNativeFile =
+    typeof File === "function" ||
+    (typeof File !== "undefined" &&
+      toString.call(File) === "[object FileConstructor]");
+  /**
+   * Returns true if obj is a Buffer, an ArrayBuffer, a Blob or a File.
+   *
+   * @private
+   */
+  function isBinary(obj) {
+    return (
+      (withNativeArrayBuffer && (obj instanceof ArrayBuffer || isView(obj))) ||
+      (withNativeBlob && obj instanceof Blob) ||
+      (withNativeFile && obj instanceof File)
+    );
+  }
+  function hasBinary(obj, toJSON) {
+    if (!obj || _typeof(obj) !== "object") {
+      return false;
+    }
+    if (Array.isArray(obj)) {
+      for (var i = 0, l = obj.length; i < l; i++) {
+        if (hasBinary(obj[i])) {
+          return true;
+        }
+      }
+      return false;
+    }
+    if (isBinary(obj)) {
+      return true;
+    }
+    if (
+      obj.toJSON &&
+      typeof obj.toJSON === "function" &&
+      arguments.length === 1
+    ) {
+      return hasBinary(obj.toJSON(), true);
+    }
+    for (var key in obj) {
+      if (
+        Object.prototype.hasOwnProperty.call(obj, key) &&
+        hasBinary(obj[key])
+      ) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Replaces every Buffer | ArrayBuffer | Blob | File in packet with a numbered placeholder.
+   *
+   * @param {Object} packet - socket.io event packet
+   * @return {Object} with deconstructed packet and list of buffers
+   * @public
+   */
+  function deconstructPacket(packet) {
+    var buffers = [];
+    var packetData = packet.data;
+    var pack = packet;
+    pack.data = _deconstructPacket(packetData, buffers);
+    pack.attachments = buffers.length; // number of binary 'attachments'
+    return {
+      packet: pack,
+      buffers: buffers,
+    };
+  }
+  function _deconstructPacket(data, buffers) {
+    if (!data) return data;
+    if (isBinary(data)) {
+      var placeholder = {
+        _placeholder: true,
+        num: buffers.length,
+      };
+      buffers.push(data);
+      return placeholder;
+    } else if (Array.isArray(data)) {
+      var newData = new Array(data.length);
+      for (var i = 0; i < data.length; i++) {
+        newData[i] = _deconstructPacket(data[i], buffers);
+      }
+      return newData;
+    } else if (_typeof(data) === "object" && !(data instanceof Date)) {
+      var _newData = {};
+      for (var key in data) {
+        if (Object.prototype.hasOwnProperty.call(data, key)) {
+          _newData[key] = _deconstructPacket(data[key], buffers);
+        }
+      }
+      return _newData;
+    }
+    return data;
+  }
+  /**
+   * Reconstructs a binary packet from its placeholder packet and buffers
+   *
+   * @param {Object} packet - event packet with placeholders
+   * @param {Array} buffers - binary buffers to put in placeholder positions
+   * @return {Object} reconstructed packet
+   * @public
+   */
+  function reconstructPacket(packet, buffers) {
+    packet.data = _reconstructPacket(packet.data, buffers);
+    delete packet.attachments; // no longer useful
+    return packet;
+  }
+  function _reconstructPacket(data, buffers) {
+    if (!data) return data;
+    if (data && data._placeholder === true) {
+      var isIndexValid =
+        typeof data.num === "number" &&
+        data.num >= 0 &&
+        data.num < buffers.length;
+      if (isIndexValid) {
+        return buffers[data.num]; // appropriate buffer (should be natural order anyway)
+      } else {
+        throw new Error("illegal attachments");
+      }
+    } else if (Array.isArray(data)) {
+      for (var i = 0; i < data.length; i++) {
+        data[i] = _reconstructPacket(data[i], buffers);
+      }
+    } else if (_typeof(data) === "object") {
+      for (var key in data) {
+        if (Object.prototype.hasOwnProperty.call(data, key)) {
+          data[key] = _reconstructPacket(data[key], buffers);
+        }
+      }
+    }
+    return data;
+  }
+
+  /**
+   * These strings must not be used as event names, as they have a special meaning.
+   */
+  var RESERVED_EVENTS$1 = [
+    "connect",
+    "connect_error",
+    "disconnect",
+    "disconnecting",
+    "newListener",
+    "removeListener", // used by the Node.js EventEmitter
+  ];
+  /**
+   * Protocol version.
+   *
+   * @public
+   */
+  var protocol = 5;
+  var PacketType;
+  (function (PacketType) {
+    PacketType[(PacketType["CONNECT"] = 0)] = "CONNECT";
+    PacketType[(PacketType["DISCONNECT"] = 1)] = "DISCONNECT";
+    PacketType[(PacketType["EVENT"] = 2)] = "EVENT";
+    PacketType[(PacketType["ACK"] = 3)] = "ACK";
+    PacketType[(PacketType["CONNECT_ERROR"] = 4)] = "CONNECT_ERROR";
+    PacketType[(PacketType["BINARY_EVENT"] = 5)] = "BINARY_EVENT";
+    PacketType[(PacketType["BINARY_ACK"] = 6)] = "BINARY_ACK";
+  })(PacketType || (PacketType = {}));
+  /**
+   * A socket.io Encoder instance
+   */
+  var Encoder = /*#__PURE__*/ (function () {
+    /**
+     * Encoder constructor
+     *
+     * @param {function} replacer - custom replacer to pass down to JSON.parse
+     */
+    function Encoder(replacer) {
+      _classCallCheck(this, Encoder);
+      this.replacer = replacer;
+    }
+    /**
+     * Encode a packet as a single string if non-binary, or as a
+     * buffer sequence, depending on packet type.
+     *
+     * @param {Object} obj - packet object
+     */
+    _createClass(Encoder, [
+      {
+        key: "encode",
+        value: function encode(obj) {
+          if (obj.type === PacketType.EVENT || obj.type === PacketType.ACK) {
+            if (hasBinary(obj)) {
+              return this.encodeAsBinary({
+                type:
+                  obj.type === PacketType.EVENT
+                    ? PacketType.BINARY_EVENT
+                    : PacketType.BINARY_ACK,
+                nsp: obj.nsp,
+                data: obj.data,
+                id: obj.id,
+              });
+            }
+          }
+          return [this.encodeAsString(obj)];
+        },
+        /**
+         * Encode packet as string.
+         */
+      },
+      {
+        key: "encodeAsString",
+        value: function encodeAsString(obj) {
+          // first is type
+          var str = "" + obj.type;
+          // attachments if we have them
+          if (
+            obj.type === PacketType.BINARY_EVENT ||
+            obj.type === PacketType.BINARY_ACK
+          ) {
+            str += obj.attachments + "-";
+          }
+          // if we have a namespace other than `/`
+          // we append it followed by a comma `,`
+          if (obj.nsp && "/" !== obj.nsp) {
+            str += obj.nsp + ",";
+          }
+          // immediately followed by the id
+          if (null != obj.id) {
+            str += obj.id;
+          }
+          // json data
+          if (null != obj.data) {
+            str += JSON.stringify(obj.data, this.replacer);
+          }
+          return str;
+        },
+        /**
+         * Encode packet as 'buffer sequence' by removing blobs, and
+         * deconstructing packet into object with placeholders and
+         * a list of buffers.
+         */
+      },
+      {
+        key: "encodeAsBinary",
+        value: function encodeAsBinary(obj) {
+          var deconstruction = deconstructPacket(obj);
+          var pack = this.encodeAsString(deconstruction.packet);
+          var buffers = deconstruction.buffers;
+          buffers.unshift(pack); // add packet info to beginning of data list
+          return buffers; // write all the buffers
+        },
+      },
+    ]);
+    return Encoder;
+  })();
+  // see https://stackoverflow.com/questions/8511281/check-if-a-value-is-an-object-in-javascript
+  function isObject(value) {
+    return Object.prototype.toString.call(value) === "[object Object]";
+  }
+  /**
+   * A socket.io Decoder instance
+   *
+   * @return {Object} decoder
+   */
+  var Decoder = /*#__PURE__*/ (function (_Emitter) {
+    _inherits(Decoder, _Emitter);
+    var _super = _createSuper(Decoder);
+    /**
+     * Decoder constructor
+     *
+     * @param {function} reviver - custom reviver to pass down to JSON.stringify
+     */
+    function Decoder(reviver) {
+      var _this;
+      _classCallCheck(this, Decoder);
+      _this = _super.call(this);
+      _this.reviver = reviver;
+      return _this;
+    }
+    /**
+     * Decodes an encoded packet string into packet JSON.
+     *
+     * @param {String} obj - encoded packet
+     */
+    _createClass(
+      Decoder,
+      [
+        {
+          key: "add",
+          value: function add(obj) {
+            var packet;
+            if (typeof obj === "string") {
+              if (this.reconstructor) {
+                throw new Error(
+                  "got plaintext data when reconstructing a packet",
+                );
+              }
+              packet = this.decodeString(obj);
+              var isBinaryEvent = packet.type === PacketType.BINARY_EVENT;
+              if (isBinaryEvent || packet.type === PacketType.BINARY_ACK) {
+                packet.type = isBinaryEvent ? PacketType.EVENT : PacketType.ACK;
+                // binary packet's json
+                this.reconstructor = new BinaryReconstructor(packet);
+                // no attachments, labeled binary but no binary data to follow
+                if (packet.attachments === 0) {
+                  _get(
+                    _getPrototypeOf(Decoder.prototype),
+                    "emitReserved",
+                    this,
+                  ).call(this, "decoded", packet);
+                }
+              } else {
+                // non-binary full packet
+                _get(
+                  _getPrototypeOf(Decoder.prototype),
+                  "emitReserved",
+                  this,
+                ).call(this, "decoded", packet);
+              }
+            } else if (isBinary(obj) || obj.base64) {
+              // raw binary data
+              if (!this.reconstructor) {
+                throw new Error(
+                  "got binary data when not reconstructing a packet",
+                );
+              } else {
+                packet = this.reconstructor.takeBinaryData(obj);
+                if (packet) {
+                  // received final buffer
+                  this.reconstructor = null;
+                  _get(
+                    _getPrototypeOf(Decoder.prototype),
+                    "emitReserved",
+                    this,
+                  ).call(this, "decoded", packet);
+                }
+              }
+            } else {
+              throw new Error("Unknown type: " + obj);
+            }
+          },
+          /**
+           * Decode a packet String (JSON data)
+           *
+           * @param {String} str
+           * @return {Object} packet
+           */
+        },
+        {
+          key: "decodeString",
+          value: function decodeString(str) {
+            var i = 0;
+            // look up type
+            var p = {
+              type: Number(str.charAt(0)),
+            };
+            if (PacketType[p.type] === undefined) {
+              throw new Error("unknown packet type " + p.type);
+            }
+            // look up attachments if type binary
+            if (
+              p.type === PacketType.BINARY_EVENT ||
+              p.type === PacketType.BINARY_ACK
+            ) {
+              var start = i + 1;
+              while (str.charAt(++i) !== "-" && i != str.length) {}
+              var buf = str.substring(start, i);
+              if (buf != Number(buf) || str.charAt(i) !== "-") {
+                throw new Error("Illegal attachments");
+              }
+              p.attachments = Number(buf);
+            }
+            // look up namespace (if any)
+            if ("/" === str.charAt(i + 1)) {
+              var _start = i + 1;
+              while (++i) {
+                var c = str.charAt(i);
+                if ("," === c) break;
+                if (i === str.length) break;
+              }
+              p.nsp = str.substring(_start, i);
+            } else {
+              p.nsp = "/";
+            }
+            // look up id
+            var next = str.charAt(i + 1);
+            if ("" !== next && Number(next) == next) {
+              var _start2 = i + 1;
+              while (++i) {
+                var _c = str.charAt(i);
+                if (null == _c || Number(_c) != _c) {
+                  --i;
+                  break;
+                }
+                if (i === str.length) break;
+              }
+              p.id = Number(str.substring(_start2, i + 1));
+            }
+            // look up json data
+            if (str.charAt(++i)) {
+              var payload = this.tryParse(str.substr(i));
+              if (Decoder.isPayloadValid(p.type, payload)) {
+                p.data = payload;
+              } else {
+                throw new Error("invalid payload");
+              }
+            }
+            return p;
+          },
+        },
+        {
+          key: "tryParse",
+          value: function tryParse(str) {
+            try {
+              return JSON.parse(str, this.reviver);
+            } catch (e) {
+              return false;
+            }
+          },
+        },
+        {
+          key: "destroy",
+          value:
+            /**
+             * Deallocates a parser's resources
+             */
+            function destroy() {
+              if (this.reconstructor) {
+                this.reconstructor.finishedReconstruction();
+                this.reconstructor = null;
+              }
+            },
+        },
+      ],
+      [
+        {
+          key: "isPayloadValid",
+          value: function isPayloadValid(type, payload) {
+            switch (type) {
+              case PacketType.CONNECT:
+                return isObject(payload);
+              case PacketType.DISCONNECT:
+                return payload === undefined;
+              case PacketType.CONNECT_ERROR:
+                return typeof payload === "string" || isObject(payload);
+              case PacketType.EVENT:
+              case PacketType.BINARY_EVENT:
+                return (
+                  Array.isArray(payload) &&
+                  (typeof payload[0] === "number" ||
+                    (typeof payload[0] === "string" &&
+                      RESERVED_EVENTS$1.indexOf(payload[0]) === -1))
+                );
+              case PacketType.ACK:
+              case PacketType.BINARY_ACK:
+                return Array.isArray(payload);
+            }
+          },
+        },
+      ],
+    );
+    return Decoder;
+  })(Emitter);
+  /**
+   * A manager of a binary event's 'buffer sequence'. Should
+   * be constructed whenever a packet of type BINARY_EVENT is
+   * decoded.
+   *
+   * @param {Object} packet
+   * @return {BinaryReconstructor} initialized reconstructor
+   */
+  var BinaryReconstructor = /*#__PURE__*/ (function () {
+    function BinaryReconstructor(packet) {
+      _classCallCheck(this, BinaryReconstructor);
+      this.packet = packet;
+      this.buffers = [];
+      this.reconPack = packet;
+    }
+    /**
+     * Method to be called when binary data received from connection
+     * after a BINARY_EVENT packet.
+     *
+     * @param {Buffer | ArrayBuffer} binData - the raw binary data received
+     * @return {null | Object} returns null if more binary data is expected or
+     *   a reconstructed packet object if all buffers have been received.
+     */
+    _createClass(BinaryReconstructor, [
+      {
+        key: "takeBinaryData",
+        value: function takeBinaryData(binData) {
+          this.buffers.push(binData);
+          if (this.buffers.length === this.reconPack.attachments) {
+            // done with buffer list
+            var packet = reconstructPacket(this.reconPack, this.buffers);
+            this.finishedReconstruction();
+            return packet;
+          }
+          return null;
+        },
+        /**
+         * Cleans up binary packet reconstruction variables.
+         */
+      },
+      {
+        key: "finishedReconstruction",
+        value: function finishedReconstruction() {
+          this.reconPack = null;
+          this.buffers = [];
+        },
+      },
+    ]);
+    return BinaryReconstructor;
+  })();
+
+  var parser = /*#__PURE__*/ Object.freeze({
+    __proto__: null,
+    protocol: protocol,
+    get PacketType() {
+      return PacketType;
+    },
+    Encoder: Encoder,
+    Decoder: Decoder,
+  });
+
+  function on(obj, ev, fn) {
+    obj.on(ev, fn);
+    return function subDestroy() {
+      obj.off(ev, fn);
+    };
+  }
+
+  /**
+   * Internal events.
+   * These events can't be emitted by the user.
+   */
+  var RESERVED_EVENTS = Object.freeze({
+    connect: 1,
+    connect_error: 1,
+    disconnect: 1,
+    disconnecting: 1,
+    // EventEmitter reserved events: https://nodejs.org/api/events.html#events_event_newlistener
+    newListener: 1,
+    removeListener: 1,
+  });
+  /**
+   * A Socket is the fundamental class for interacting with the server.
+   *
+   * A Socket belongs to a certain Namespace (by default /) and uses an underlying {@link Manager} to communicate.
+   *
+   * @example
+   * const socket = io();
+   *
+   * socket.on("connect", () => {
+   *   console.log("connected");
+   * });
+   *
+   * // send an event to the server
+   * socket.emit("foo", "bar");
+   *
+   * socket.on("foobar", () => {
+   *   // an event was received from the server
+   * });
+   *
+   * // upon disconnection
+   * socket.on("disconnect", (reason) => {
+   *   console.log(`disconnected due to ${reason}`);
+   * });
+   */
+  var Socket = /*#__PURE__*/ (function (_Emitter) {
+    _inherits(Socket, _Emitter);
+    var _super = _createSuper(Socket);
+    /**
+     * `Socket` constructor.
+     */
+    function Socket(io, nsp, opts) {
+      var _this;
+      _classCallCheck(this, Socket);
+      _this = _super.call(this);
+      /**
+       * Whether the socket is currently connected to the server.
+       *
+       * @example
+       * const socket = io();
+       *
+       * socket.on("connect", () => {
+       *   console.log(socket.connected); // true
+       * });
+       *
+       * socket.on("disconnect", () => {
+       *   console.log(socket.connected); // false
+       * });
+       */
+      _this.connected = false;
+      /**
+       * Whether the connection state was recovered after a temporary disconnection. In that case, any missed packets will
+       * be transmitted by the server.
+       */
+      _this.recovered = false;
+      /**
+       * Buffer for packets received before the CONNECT packet
+       */
+      _this.receiveBuffer = [];
+      /**
+       * Buffer for packets that will be sent once the socket is connected
+       */
+      _this.sendBuffer = [];
+      /**
+       * The queue of packets to be sent with retry in case of failure.
+       *
+       * Packets are sent one by one, each waiting for the server acknowledgement, in order to guarantee the delivery order.
+       * @private
+       */
+      _this._queue = [];
+      /**
+       * A sequence to generate the ID of the {@link QueuedPacket}.
+       * @private
+       */
+      _this._queueSeq = 0;
+      _this.ids = 0;
+      /**
+       * A map containing acknowledgement handlers.
+       *
+       * The `withError` attribute is used to differentiate handlers that accept an error as first argument:
+       *
+       * - `socket.emit("test", (err, value) => { ... })` with `ackTimeout` option
+       * - `socket.timeout(5000).emit("test", (err, value) => { ... })`
+       * - `const value = await socket.emitWithAck("test")`
+       *
+       * From those that don't:
+       *
+       * - `socket.emit("test", (value) => { ... });`
+       *
+       * In the first case, the handlers will be called with an error when:
+       *
+       * - the timeout is reached
+       * - the socket gets disconnected
+       *
+       * In the second case, the handlers will be simply discarded upon disconnection, since the client will never receive
+       * an acknowledgement from the server.
+       *
+       * @private
+       */
+      _this.acks = {};
+      _this.flags = {};
+      _this.io = io;
+      _this.nsp = nsp;
+      if (opts && opts.auth) {
+        _this.auth = opts.auth;
+      }
+      _this._opts = _extends({}, opts);
+      if (_this.io._autoConnect) _this.open();
+      return _this;
+    }
+    /**
+     * Whether the socket is currently disconnected
+     *
+     * @example
+     * const socket = io();
+     *
+     * socket.on("connect", () => {
+     *   console.log(socket.disconnected); // false
+     * });
+     *
+     * socket.on("disconnect", () => {
+     *   console.log(socket.disconnected); // true
+     * });
+     */
+    _createClass(Socket, [
+      {
+        key: "disconnected",
+        get: function get() {
+          return !this.connected;
+        },
+        /**
+         * Subscribe to open, close and packet events
+         *
+         * @private
+         */
+      },
+      {
+        key: "subEvents",
+        value: function subEvents() {
+          if (this.subs) return;
+          var io = this.io;
+          this.subs = [
+            on(io, "open", this.onopen.bind(this)),
+            on(io, "packet", this.onpacket.bind(this)),
+            on(io, "error", this.onerror.bind(this)),
+            on(io, "close", this.onclose.bind(this)),
+          ];
+        },
+        /**
+         * Whether the Socket will try to reconnect when its Manager connects or reconnects.
+         *
+         * @example
+         * const socket = io();
+         *
+         * console.log(socket.active); // true
+         *
+         * socket.on("disconnect", (reason) => {
+         *   if (reason === "io server disconnect") {
+         *     // the disconnection was initiated by the server, you need to manually reconnect
+         *     console.log(socket.active); // false
+         *   }
+         *   // else the socket will automatically try to reconnect
+         *   console.log(socket.active); // true
+         * });
+         */
+      },
+      {
+        key: "active",
+        get: function get() {
+          return !!this.subs;
+        },
+        /**
+         * "Opens" the socket.
+         *
+         * @example
+         * const socket = io({
+         *   autoConnect: false
+         * });
+         *
+         * socket.connect();
+         */
+      },
+      {
+        key: "connect",
+        value: function connect() {
+          if (this.connected) return this;
+          this.subEvents();
+          if (!this.io["_reconnecting"]) this.io.open(); // ensure open
+          if ("open" === this.io._readyState) this.onopen();
+          return this;
+        },
+        /**
+         * Alias for {@link connect()}.
+         */
+      },
+      {
+        key: "open",
+        value: function open() {
+          return this.connect();
+        },
+        /**
+         * Sends a `message` event.
+         *
+         * This method mimics the WebSocket.send() method.
+         *
+         * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send
+         *
+         * @example
+         * socket.send("hello");
+         *
+         * // this is equivalent to
+         * socket.emit("message", "hello");
+         *
+         * @return self
+         */
+      },
+      {
+        key: "send",
+        value: function send() {
+          for (
+            var _len = arguments.length, args = new Array(_len), _key = 0;
+            _key < _len;
+            _key++
+          ) {
+            args[_key] = arguments[_key];
+          }
+          args.unshift("message");
+          this.emit.apply(this, args);
+          return this;
+        },
+        /**
+         * Override `emit`.
+         * If the event is in `events`, it's emitted normally.
+         *
+         * @example
+         * socket.emit("hello", "world");
+         *
+         * // all serializable datastructures are supported (no need to call JSON.stringify)
+         * socket.emit("hello", 1, "2", { 3: ["4"], 5: Uint8Array.from([6]) });
+         *
+         * // with an acknowledgement from the server
+         * socket.emit("hello", "world", (val) => {
+         *   // ...
+         * });
+         *
+         * @return self
+         */
+      },
+      {
+        key: "emit",
+        value: function emit(ev) {
+          if (RESERVED_EVENTS.hasOwnProperty(ev)) {
+            throw new Error('"' + ev.toString() + '" is a reserved event name');
+          }
+          for (
+            var _len2 = arguments.length,
+              args = new Array(_len2 > 1 ? _len2 - 1 : 0),
+              _key2 = 1;
+            _key2 < _len2;
+            _key2++
+          ) {
+            args[_key2 - 1] = arguments[_key2];
+          }
+          args.unshift(ev);
+          if (
+            this._opts.retries &&
+            !this.flags.fromQueue &&
+            !this.flags["volatile"]
+          ) {
+            this._addToQueue(args);
+            return this;
+          }
+          var packet = {
+            type: PacketType.EVENT,
+            data: args,
+          };
+          packet.options = {};
+          packet.options.compress = this.flags.compress !== false;
+          // event ack callback
+          if ("function" === typeof args[args.length - 1]) {
+            var id = this.ids++;
+            var ack = args.pop();
+            this._registerAckCallback(id, ack);
+            packet.id = id;
+          }
+          var isTransportWritable =
+            this.io.engine &&
+            this.io.engine.transport &&
+            this.io.engine.transport.writable;
+          var discardPacket =
+            this.flags["volatile"] && (!isTransportWritable || !this.connected);
+          if (discardPacket);
+          else if (this.connected) {
+            this.notifyOutgoingListeners(packet);
+            this.packet(packet);
+          } else {
+            this.sendBuffer.push(packet);
+          }
+          this.flags = {};
+          return this;
+        },
+        /**
+         * @private
+         */
+      },
+      {
+        key: "_registerAckCallback",
+        value: function _registerAckCallback(id, ack) {
+          var _this2 = this;
+          var _a;
+          var timeout =
+            (_a = this.flags.timeout) !== null && _a !== void 0
+              ? _a
+              : this._opts.ackTimeout;
+          if (timeout === undefined) {
+            this.acks[id] = ack;
+            return;
+          }
+          // @ts-ignore
+          var timer = this.io.setTimeoutFn(function () {
+            delete _this2.acks[id];
+            for (var i = 0; i < _this2.sendBuffer.length; i++) {
+              if (_this2.sendBuffer[i].id === id) {
+                _this2.sendBuffer.splice(i, 1);
+              }
+            }
+            ack.call(_this2, new Error("operation has timed out"));
+          }, timeout);
+          var fn = function fn() {
+            // @ts-ignore
+            _this2.io.clearTimeoutFn(timer);
+            for (
+              var _len3 = arguments.length, args = new Array(_len3), _key3 = 0;
+              _key3 < _len3;
+              _key3++
+            ) {
+              args[_key3] = arguments[_key3];
+            }
+            ack.apply(_this2, args);
+          };
+          fn.withError = true;
+          this.acks[id] = fn;
+        },
+        /**
+         * Emits an event and waits for an acknowledgement
+         *
+         * @example
+         * // without timeout
+         * const response = await socket.emitWithAck("hello", "world");
+         *
+         * // with a specific timeout
+         * try {
+         *   const response = await socket.timeout(1000).emitWithAck("hello", "world");
+         * } catch (err) {
+         *   // the server did not acknowledge the event in the given delay
+         * }
+         *
+         * @return a Promise that will be fulfilled when the server acknowledges the event
+         */
+      },
+      {
+        key: "emitWithAck",
+        value: function emitWithAck(ev) {
+          var _this3 = this;
+          for (
+            var _len4 = arguments.length,
+              args = new Array(_len4 > 1 ? _len4 - 1 : 0),
+              _key4 = 1;
+            _key4 < _len4;
+            _key4++
+          ) {
+            args[_key4 - 1] = arguments[_key4];
+          }
+          return new Promise(function (resolve, reject) {
+            var fn = function fn(arg1, arg2) {
+              return arg1 ? reject(arg1) : resolve(arg2);
+            };
+            fn.withError = true;
+            args.push(fn);
+            _this3.emit.apply(_this3, [ev].concat(args));
+          });
+        },
+        /**
+         * Add the packet to the queue.
+         * @param args
+         * @private
+         */
+      },
+      {
+        key: "_addToQueue",
+        value: function _addToQueue(args) {
+          var _this4 = this;
+          var ack;
+          if (typeof args[args.length - 1] === "function") {
+            ack = args.pop();
+          }
+          var packet = {
+            id: this._queueSeq++,
+            tryCount: 0,
+            pending: false,
+            args: args,
+            flags: _extends(
+              {
+                fromQueue: true,
+              },
+              this.flags,
+            ),
+          };
+          args.push(function (err) {
+            if (packet !== _this4._queue[0]) {
+              // the packet has already been acknowledged
+              return;
+            }
+            var hasError = err !== null;
+            if (hasError) {
+              if (packet.tryCount > _this4._opts.retries) {
+                _this4._queue.shift();
+                if (ack) {
+                  ack(err);
+                }
+              }
+            } else {
+              _this4._queue.shift();
+              if (ack) {
+                for (
+                  var _len5 = arguments.length,
+                    responseArgs = new Array(_len5 > 1 ? _len5 - 1 : 0),
+                    _key5 = 1;
+                  _key5 < _len5;
+                  _key5++
+                ) {
+                  responseArgs[_key5 - 1] = arguments[_key5];
+                }
+                ack.apply(void 0, [null].concat(responseArgs));
+              }
+            }
+            packet.pending = false;
+            return _this4._drainQueue();
+          });
+          this._queue.push(packet);
+          this._drainQueue();
+        },
+        /**
+         * Send the first packet of the queue, and wait for an acknowledgement from the server.
+         * @param force - whether to resend a packet that has not been acknowledged yet
+         *
+         * @private
+         */
+      },
+      {
+        key: "_drainQueue",
+        value: function _drainQueue() {
+          var force =
+            arguments.length > 0 && arguments[0] !== undefined
+              ? arguments[0]
+              : false;
+          if (!this.connected || this._queue.length === 0) {
+            return;
+          }
+          var packet = this._queue[0];
+          if (packet.pending && !force) {
+            return;
+          }
+          packet.pending = true;
+          packet.tryCount++;
+          this.flags = packet.flags;
+          this.emit.apply(this, packet.args);
+        },
+        /**
+         * Sends a packet.
+         *
+         * @param packet
+         * @private
+         */
+      },
+      {
+        key: "packet",
+        value: function packet(_packet) {
+          _packet.nsp = this.nsp;
+          this.io._packet(_packet);
+        },
+        /**
+         * Called upon engine `open`.
+         *
+         * @private
+         */
+      },
+      {
+        key: "onopen",
+        value: function onopen() {
+          var _this5 = this;
+          if (typeof this.auth == "function") {
+            this.auth(function (data) {
+              _this5._sendConnectPacket(data);
+            });
+          } else {
+            this._sendConnectPacket(this.auth);
+          }
+        },
+        /**
+         * Sends a CONNECT packet to initiate the Socket.IO session.
+         *
+         * @param data
+         * @private
+         */
+      },
+      {
+        key: "_sendConnectPacket",
+        value: function _sendConnectPacket(data) {
+          this.packet({
+            type: PacketType.CONNECT,
+            data: this._pid
+              ? _extends(
+                  {
+                    pid: this._pid,
+                    offset: this._lastOffset,
+                  },
+                  data,
+                )
+              : data,
+          });
+        },
+        /**
+         * Called upon engine or manager `error`.
+         *
+         * @param err
+         * @private
+         */
+      },
+      {
+        key: "onerror",
+        value: function onerror(err) {
+          if (!this.connected) {
+            this.emitReserved("connect_error", err);
+          }
+        },
+        /**
+         * Called upon engine `close`.
+         *
+         * @param reason
+         * @param description
+         * @private
+         */
+      },
+      {
+        key: "onclose",
+        value: function onclose(reason, description) {
+          this.connected = false;
+          delete this.id;
+          this.emitReserved("disconnect", reason, description);
+          this._clearAcks();
+        },
+        /**
+         * Clears the acknowledgement handlers upon disconnection, since the client will never receive an acknowledgement from
+         * the server.
+         *
+         * @private
+         */
+      },
+      {
+        key: "_clearAcks",
+        value: function _clearAcks() {
+          var _this6 = this;
+          Object.keys(this.acks).forEach(function (id) {
+            var isBuffered = _this6.sendBuffer.some(function (packet) {
+              return String(packet.id) === id;
+            });
+            if (!isBuffered) {
+              // note: handlers that do not accept an error as first argument are ignored here
+              var ack = _this6.acks[id];
+              delete _this6.acks[id];
+              if (ack.withError) {
+                ack.call(_this6, new Error("socket has been disconnected"));
+              }
+            }
+          });
+        },
+        /**
+         * Called with socket packet.
+         *
+         * @param packet
+         * @private
+         */
+      },
+      {
+        key: "onpacket",
+        value: function onpacket(packet) {
+          var sameNamespace = packet.nsp === this.nsp;
+          if (!sameNamespace) return;
+          switch (packet.type) {
+            case PacketType.CONNECT:
+              if (packet.data && packet.data.sid) {
+                this.onconnect(packet.data.sid, packet.data.pid);
+              } else {
+                this.emitReserved(
+                  "connect_error",
+                  new Error(
+                    "It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)",
+                  ),
+                );
+              }
+              break;
+            case PacketType.EVENT:
+            case PacketType.BINARY_EVENT:
+              this.onevent(packet);
+              break;
+            case PacketType.ACK:
+            case PacketType.BINARY_ACK:
+              this.onack(packet);
+              break;
+            case PacketType.DISCONNECT:
+              this.ondisconnect();
+              break;
+            case PacketType.CONNECT_ERROR:
+              this.destroy();
+              var err = new Error(packet.data.message);
+              // @ts-ignore
+              err.data = packet.data.data;
+              this.emitReserved("connect_error", err);
+              break;
+          }
+        },
+        /**
+         * Called upon a server event.
+         *
+         * @param packet
+         * @private
+         */
+      },
+      {
+        key: "onevent",
+        value: function onevent(packet) {
+          var args = packet.data || [];
+          if (null != packet.id) {
+            args.push(this.ack(packet.id));
+          }
+          if (this.connected) {
+            this.emitEvent(args);
+          } else {
+            this.receiveBuffer.push(Object.freeze(args));
+          }
+        },
+      },
+      {
+        key: "emitEvent",
+        value: function emitEvent(args) {
+          if (this._anyListeners && this._anyListeners.length) {
+            var listeners = this._anyListeners.slice();
+            var _iterator = _createForOfIteratorHelper(listeners),
+              _step;
+            try {
+              for (_iterator.s(); !(_step = _iterator.n()).done; ) {
+                var listener = _step.value;
+                listener.apply(this, args);
+              }
+            } catch (err) {
+              _iterator.e(err);
+            } finally {
+              _iterator.f();
+            }
+          }
+          _get(_getPrototypeOf(Socket.prototype), "emit", this).apply(
+            this,
+            args,
+          );
+          if (
+            this._pid &&
+            args.length &&
+            typeof args[args.length - 1] === "string"
+          ) {
+            this._lastOffset = args[args.length - 1];
+          }
+        },
+        /**
+         * Produces an ack callback to emit with an event.
+         *
+         * @private
+         */
+      },
+      {
+        key: "ack",
+        value: function ack(id) {
+          var self = this;
+          var sent = false;
+          return function () {
+            // prevent double callbacks
+            if (sent) return;
+            sent = true;
+            for (
+              var _len6 = arguments.length, args = new Array(_len6), _key6 = 0;
+              _key6 < _len6;
+              _key6++
+            ) {
+              args[_key6] = arguments[_key6];
+            }
+            self.packet({
+              type: PacketType.ACK,
+              id: id,
+              data: args,
+            });
+          };
+        },
+        /**
+         * Called upon a server acknowledgement.
+         *
+         * @param packet
+         * @private
+         */
+      },
+      {
+        key: "onack",
+        value: function onack(packet) {
+          var ack = this.acks[packet.id];
+          if (typeof ack !== "function") {
+            return;
+          }
+          delete this.acks[packet.id];
+          // @ts-ignore FIXME ack is incorrectly inferred as 'never'
+          if (ack.withError) {
+            packet.data.unshift(null);
+          }
+          // @ts-ignore
+          ack.apply(this, packet.data);
+        },
+        /**
+         * Called upon server connect.
+         *
+         * @private
+         */
+      },
+      {
+        key: "onconnect",
+        value: function onconnect(id, pid) {
+          this.id = id;
+          this.recovered = pid && this._pid === pid;
+          this._pid = pid; // defined only if connection state recovery is enabled
+          this.connected = true;
+          this.emitBuffered();
+          this.emitReserved("connect");
+          this._drainQueue(true);
+        },
+        /**
+         * Emit buffered events (received and emitted).
+         *
+         * @private
+         */
+      },
+      {
+        key: "emitBuffered",
+        value: function emitBuffered() {
+          var _this7 = this;
+          this.receiveBuffer.forEach(function (args) {
+            return _this7.emitEvent(args);
+          });
+          this.receiveBuffer = [];
+          this.sendBuffer.forEach(function (packet) {
+            _this7.notifyOutgoingListeners(packet);
+            _this7.packet(packet);
+          });
+          this.sendBuffer = [];
+        },
+        /**
+         * Called upon server disconnect.
+         *
+         * @private
+         */
+      },
+      {
+        key: "ondisconnect",
+        value: function ondisconnect() {
+          this.destroy();
+          this.onclose("io server disconnect");
+        },
+        /**
+         * Called upon forced client/server side disconnections,
+         * this method ensures the manager stops tracking us and
+         * that reconnections don't get triggered for this.
+         *
+         * @private
+         */
+      },
+      {
+        key: "destroy",
+        value: function destroy() {
+          if (this.subs) {
+            // clean subscriptions to avoid reconnections
+            this.subs.forEach(function (subDestroy) {
+              return subDestroy();
+            });
+            this.subs = undefined;
+          }
+          this.io["_destroy"](this);
+        },
+        /**
+         * Disconnects the socket manually. In that case, the socket will not try to reconnect.
+         *
+         * If this is the last active Socket instance of the {@link Manager}, the low-level connection will be closed.
+         *
+         * @example
+         * const socket = io();
+         *
+         * socket.on("disconnect", (reason) => {
+         *   // console.log(reason); prints "io client disconnect"
+         * });
+         *
+         * socket.disconnect();
+         *
+         * @return self
+         */
+      },
+      {
+        key: "disconnect",
+        value: function disconnect() {
+          if (this.connected) {
+            this.packet({
+              type: PacketType.DISCONNECT,
+            });
+          }
+          // remove socket from pool
+          this.destroy();
+          if (this.connected) {
+            // fire events
+            this.onclose("io client disconnect");
+          }
+          return this;
+        },
+        /**
+         * Alias for {@link disconnect()}.
+         *
+         * @return self
+         */
+      },
+      {
+        key: "close",
+        value: function close() {
+          return this.disconnect();
+        },
+        /**
+         * Sets the compress flag.
+         *
+         * @example
+         * socket.compress(false).emit("hello");
+         *
+         * @param compress - if `true`, compresses the sending data
+         * @return self
+         */
+      },
+      {
+        key: "compress",
+        value: function compress(_compress) {
+          this.flags.compress = _compress;
+          return this;
+        },
+        /**
+         * Sets a modifier for a subsequent event emission that the event message will be dropped when this socket is not
+         * ready to send messages.
+         *
+         * @example
+         * socket.volatile.emit("hello"); // the server may or may not receive it
+         *
+         * @returns self
+         */
+      },
+      {
+        key: "volatile",
+        get: function get() {
+          this.flags["volatile"] = true;
+          return this;
+        },
+        /**
+         * Sets a modifier for a subsequent event emission that the callback will be called with an error when the
+         * given number of milliseconds have elapsed without an acknowledgement from the server:
+         *
+         * @example
+         * socket.timeout(5000).emit("my-event", (err) => {
+         *   if (err) {
+         *     // the server did not acknowledge the event in the given delay
+         *   }
+         * });
+         *
+         * @returns self
+         */
+      },
+      {
+        key: "timeout",
+        value: function timeout(_timeout) {
+          this.flags.timeout = _timeout;
+          return this;
+        },
+        /**
+         * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
+         * callback.
+         *
+         * @example
+         * socket.onAny((event, ...args) => {
+         *   console.log(`got ${event}`);
+         * });
+         *
+         * @param listener
+         */
+      },
+      {
+        key: "onAny",
+        value: function onAny(listener) {
+          this._anyListeners = this._anyListeners || [];
+          this._anyListeners.push(listener);
+          return this;
+        },
+        /**
+         * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
+         * callback. The listener is added to the beginning of the listeners array.
+         *
+         * @example
+         * socket.prependAny((event, ...args) => {
+         *   console.log(`got event ${event}`);
+         * });
+         *
+         * @param listener
+         */
+      },
+      {
+        key: "prependAny",
+        value: function prependAny(listener) {
+          this._anyListeners = this._anyListeners || [];
+          this._anyListeners.unshift(listener);
+          return this;
+        },
+        /**
+         * Removes the listener that will be fired when any event is emitted.
+         *
+         * @example
+         * const catchAllListener = (event, ...args) => {
+         *   console.log(`got event ${event}`);
+         * }
+         *
+         * socket.onAny(catchAllListener);
+         *
+         * // remove a specific listener
+         * socket.offAny(catchAllListener);
+         *
+         * // or remove all listeners
+         * socket.offAny();
+         *
+         * @param listener
+         */
+      },
+      {
+        key: "offAny",
+        value: function offAny(listener) {
+          if (!this._anyListeners) {
+            return this;
+          }
+          if (listener) {
+            var listeners = this._anyListeners;
+            for (var i = 0; i < listeners.length; i++) {
+              if (listener === listeners[i]) {
+                listeners.splice(i, 1);
+                return this;
+              }
+            }
+          } else {
+            this._anyListeners = [];
+          }
+          return this;
+        },
+        /**
+         * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,
+         * e.g. to remove listeners.
+         */
+      },
+      {
+        key: "listenersAny",
+        value: function listenersAny() {
+          return this._anyListeners || [];
+        },
+        /**
+         * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
+         * callback.
+         *
+         * Note: acknowledgements sent to the server are not included.
+         *
+         * @example
+         * socket.onAnyOutgoing((event, ...args) => {
+         *   console.log(`sent event ${event}`);
+         * });
+         *
+         * @param listener
+         */
+      },
+      {
+        key: "onAnyOutgoing",
+        value: function onAnyOutgoing(listener) {
+          this._anyOutgoingListeners = this._anyOutgoingListeners || [];
+          this._anyOutgoingListeners.push(listener);
+          return this;
+        },
+        /**
+         * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
+         * callback. The listener is added to the beginning of the listeners array.
+         *
+         * Note: acknowledgements sent to the server are not included.
+         *
+         * @example
+         * socket.prependAnyOutgoing((event, ...args) => {
+         *   console.log(`sent event ${event}`);
+         * });
+         *
+         * @param listener
+         */
+      },
+      {
+        key: "prependAnyOutgoing",
+        value: function prependAnyOutgoing(listener) {
+          this._anyOutgoingListeners = this._anyOutgoingListeners || [];
+          this._anyOutgoingListeners.unshift(listener);
+          return this;
+        },
+        /**
+         * Removes the listener that will be fired when any event is emitted.
+         *
+         * @example
+         * const catchAllListener = (event, ...args) => {
+         *   console.log(`sent event ${event}`);
+         * }
+         *
+         * socket.onAnyOutgoing(catchAllListener);
+         *
+         * // remove a specific listener
+         * socket.offAnyOutgoing(catchAllListener);
+         *
+         * // or remove all listeners
+         * socket.offAnyOutgoing();
+         *
+         * @param [listener] - the catch-all listener (optional)
+         */
+      },
+      {
+        key: "offAnyOutgoing",
+        value: function offAnyOutgoing(listener) {
+          if (!this._anyOutgoingListeners) {
+            return this;
+          }
+          if (listener) {
+            var listeners = this._anyOutgoingListeners;
+            for (var i = 0; i < listeners.length; i++) {
+              if (listener === listeners[i]) {
+                listeners.splice(i, 1);
+                return this;
+              }
+            }
+          } else {
+            this._anyOutgoingListeners = [];
+          }
+          return this;
+        },
+        /**
+         * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,
+         * e.g. to remove listeners.
+         */
+      },
+      {
+        key: "listenersAnyOutgoing",
+        value: function listenersAnyOutgoing() {
+          return this._anyOutgoingListeners || [];
+        },
+        /**
+         * Notify the listeners for each packet sent
+         *
+         * @param packet
+         *
+         * @private
+         */
+      },
+      {
+        key: "notifyOutgoingListeners",
+        value: function notifyOutgoingListeners(packet) {
+          if (this._anyOutgoingListeners && this._anyOutgoingListeners.length) {
+            var listeners = this._anyOutgoingListeners.slice();
+            var _iterator2 = _createForOfIteratorHelper(listeners),
+              _step2;
+            try {
+              for (_iterator2.s(); !(_step2 = _iterator2.n()).done; ) {
+                var listener = _step2.value;
+                listener.apply(this, packet.data);
+              }
+            } catch (err) {
+              _iterator2.e(err);
+            } finally {
+              _iterator2.f();
+            }
+          }
+        },
+      },
+    ]);
+    return Socket;
+  })(Emitter);
+
+  /**
+   * Initialize backoff timer with `opts`.
+   *
+   * - `min` initial timeout in milliseconds [100]
+   * - `max` max timeout [10000]
+   * - `jitter` [0]
+   * - `factor` [2]
+   *
+   * @param {Object} opts
+   * @api public
+   */
+  function Backoff(opts) {
+    opts = opts || {};
+    this.ms = opts.min || 100;
+    this.max = opts.max || 10000;
+    this.factor = opts.factor || 2;
+    this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0;
+    this.attempts = 0;
+  }
+  /**
+   * Return the backoff duration.
+   *
+   * @return {Number}
+   * @api public
+   */
+  Backoff.prototype.duration = function () {
+    var ms = this.ms * Math.pow(this.factor, this.attempts++);
+    if (this.jitter) {
+      var rand = Math.random();
+      var deviation = Math.floor(rand * this.jitter * ms);
+      ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation;
+    }
+    return Math.min(ms, this.max) | 0;
+  };
+  /**
+   * Reset the number of attempts.
+   *
+   * @api public
+   */
+  Backoff.prototype.reset = function () {
+    this.attempts = 0;
+  };
+  /**
+   * Set the minimum duration
+   *
+   * @api public
+   */
+  Backoff.prototype.setMin = function (min) {
+    this.ms = min;
+  };
+  /**
+   * Set the maximum duration
+   *
+   * @api public
+   */
+  Backoff.prototype.setMax = function (max) {
+    this.max = max;
+  };
+  /**
+   * Set the jitter
+   *
+   * @api public
+   */
+  Backoff.prototype.setJitter = function (jitter) {
+    this.jitter = jitter;
+  };
+
+  var Manager = /*#__PURE__*/ (function (_Emitter) {
+    _inherits(Manager, _Emitter);
+    var _super = _createSuper(Manager);
+    function Manager(uri, opts) {
+      var _this;
+      _classCallCheck(this, Manager);
+      var _a;
+      _this = _super.call(this);
+      _this.nsps = {};
+      _this.subs = [];
+      if (uri && "object" === _typeof(uri)) {
+        opts = uri;
+        uri = undefined;
+      }
+      opts = opts || {};
+      opts.path = opts.path || "/socket.io";
+      _this.opts = opts;
+      installTimerFunctions(_assertThisInitialized(_this), opts);
+      _this.reconnection(opts.reconnection !== false);
+      _this.reconnectionAttempts(opts.reconnectionAttempts || Infinity);
+      _this.reconnectionDelay(opts.reconnectionDelay || 1000);
+      _this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000);
+      _this.randomizationFactor(
+        (_a = opts.randomizationFactor) !== null && _a !== void 0 ? _a : 0.5,
+      );
+      _this.backoff = new Backoff({
+        min: _this.reconnectionDelay(),
+        max: _this.reconnectionDelayMax(),
+        jitter: _this.randomizationFactor(),
+      });
+      _this.timeout(null == opts.timeout ? 20000 : opts.timeout);
+      _this._readyState = "closed";
+      _this.uri = uri;
+      var _parser = opts.parser || parser;
+      _this.encoder = new _parser.Encoder();
+      _this.decoder = new _parser.Decoder();
+      _this._autoConnect = opts.autoConnect !== false;
+      if (_this._autoConnect) _this.open();
+      return _this;
+    }
+    _createClass(Manager, [
+      {
+        key: "reconnection",
+        value: function reconnection(v) {
+          if (!arguments.length) return this._reconnection;
+          this._reconnection = !!v;
+          return this;
+        },
+      },
+      {
+        key: "reconnectionAttempts",
+        value: function reconnectionAttempts(v) {
+          if (v === undefined) return this._reconnectionAttempts;
+          this._reconnectionAttempts = v;
+          return this;
+        },
+      },
+      {
+        key: "reconnectionDelay",
+        value: function reconnectionDelay(v) {
+          var _a;
+          if (v === undefined) return this._reconnectionDelay;
+          this._reconnectionDelay = v;
+          (_a = this.backoff) === null || _a === void 0 ? void 0 : _a.setMin(v);
+          return this;
+        },
+      },
+      {
+        key: "randomizationFactor",
+        value: function randomizationFactor(v) {
+          var _a;
+          if (v === undefined) return this._randomizationFactor;
+          this._randomizationFactor = v;
+          (_a = this.backoff) === null || _a === void 0
+            ? void 0
+            : _a.setJitter(v);
+          return this;
+        },
+      },
+      {
+        key: "reconnectionDelayMax",
+        value: function reconnectionDelayMax(v) {
+          var _a;
+          if (v === undefined) return this._reconnectionDelayMax;
+          this._reconnectionDelayMax = v;
+          (_a = this.backoff) === null || _a === void 0 ? void 0 : _a.setMax(v);
+          return this;
+        },
+      },
+      {
+        key: "timeout",
+        value: function timeout(v) {
+          if (!arguments.length) return this._timeout;
+          this._timeout = v;
+          return this;
+        },
+        /**
+         * Starts trying to reconnect if reconnection is enabled and we have not
+         * started reconnecting yet
+         *
+         * @private
+         */
+      },
+      {
+        key: "maybeReconnectOnOpen",
+        value: function maybeReconnectOnOpen() {
+          // Only try to reconnect if it's the first time we're connecting
+          if (
+            !this._reconnecting &&
+            this._reconnection &&
+            this.backoff.attempts === 0
+          ) {
+            // keeps reconnection from firing twice for the same reconnection loop
+            this.reconnect();
+          }
+        },
+        /**
+         * Sets the current transport `socket`.
+         *
+         * @param {Function} fn - optional, callback
+         * @return self
+         * @public
+         */
+      },
+      {
+        key: "open",
+        value: function open(fn) {
+          var _this2 = this;
+          if (~this._readyState.indexOf("open")) return this;
+          this.engine = new Socket$1(this.uri, this.opts);
+          var socket = this.engine;
+          var self = this;
+          this._readyState = "opening";
+          this.skipReconnect = false;
+          // emit `open`
+          var openSubDestroy = on(socket, "open", function () {
+            self.onopen();
+            fn && fn();
+          });
+          var onError = function onError(err) {
+            _this2.cleanup();
+            _this2._readyState = "closed";
+            _this2.emitReserved("error", err);
+            if (fn) {
+              fn(err);
+            } else {
+              // Only do this if there is no fn to handle the error
+              _this2.maybeReconnectOnOpen();
+            }
+          };
+          // emit `error`
+          var errorSub = on(socket, "error", onError);
+          if (false !== this._timeout) {
+            var timeout = this._timeout;
+            // set timer
+            var timer = this.setTimeoutFn(function () {
+              openSubDestroy();
+              onError(new Error("timeout"));
+              socket.close();
+            }, timeout);
+            if (this.opts.autoUnref) {
+              timer.unref();
+            }
+            this.subs.push(function () {
+              _this2.clearTimeoutFn(timer);
+            });
+          }
+          this.subs.push(openSubDestroy);
+          this.subs.push(errorSub);
+          return this;
+        },
+        /**
+         * Alias for open()
+         *
+         * @return self
+         * @public
+         */
+      },
+      {
+        key: "connect",
+        value: function connect(fn) {
+          return this.open(fn);
+        },
+        /**
+         * Called upon transport open.
+         *
+         * @private
+         */
+      },
+      {
+        key: "onopen",
+        value: function onopen() {
+          // clear old subs
+          this.cleanup();
+          // mark as open
+          this._readyState = "open";
+          this.emitReserved("open");
+          // add new subs
+          var socket = this.engine;
+          this.subs.push(
+            on(socket, "ping", this.onping.bind(this)),
+            on(socket, "data", this.ondata.bind(this)),
+            on(socket, "error", this.onerror.bind(this)),
+            on(socket, "close", this.onclose.bind(this)),
+            on(this.decoder, "decoded", this.ondecoded.bind(this)),
+          );
+        },
+        /**
+         * Called upon a ping.
+         *
+         * @private
+         */
+      },
+      {
+        key: "onping",
+        value: function onping() {
+          this.emitReserved("ping");
+        },
+        /**
+         * Called with data.
+         *
+         * @private
+         */
+      },
+      {
+        key: "ondata",
+        value: function ondata(data) {
+          try {
+            this.decoder.add(data);
+          } catch (e) {
+            this.onclose("parse error", e);
+          }
+        },
+        /**
+         * Called when parser fully decodes a packet.
+         *
+         * @private
+         */
+      },
+      {
+        key: "ondecoded",
+        value: function ondecoded(packet) {
+          var _this3 = this;
+          // the nextTick call prevents an exception in a user-provided event listener from triggering a disconnection due to a "parse error"
+          nextTick(function () {
+            _this3.emitReserved("packet", packet);
+          }, this.setTimeoutFn);
+        },
+        /**
+         * Called upon socket error.
+         *
+         * @private
+         */
+      },
+      {
+        key: "onerror",
+        value: function onerror(err) {
+          this.emitReserved("error", err);
+        },
+        /**
+         * Creates a new socket for the given `nsp`.
+         *
+         * @return {Socket}
+         * @public
+         */
+      },
+      {
+        key: "socket",
+        value: function socket(nsp, opts) {
+          var socket = this.nsps[nsp];
+          if (!socket) {
+            socket = new Socket(this, nsp, opts);
+            this.nsps[nsp] = socket;
+          } else if (this._autoConnect && !socket.active) {
+            socket.connect();
+          }
+          return socket;
+        },
+        /**
+         * Called upon a socket close.
+         *
+         * @param socket
+         * @private
+         */
+      },
+      {
+        key: "_destroy",
+        value: function _destroy(socket) {
+          var nsps = Object.keys(this.nsps);
+          for (var _i = 0, _nsps = nsps; _i < _nsps.length; _i++) {
+            var nsp = _nsps[_i];
+            var _socket = this.nsps[nsp];
+            if (_socket.active) {
+              return;
+            }
+          }
+          this._close();
+        },
+        /**
+         * Writes a packet.
+         *
+         * @param packet
+         * @private
+         */
+      },
+      {
+        key: "_packet",
+        value: function _packet(packet) {
+          var encodedPackets = this.encoder.encode(packet);
+          for (var i = 0; i < encodedPackets.length; i++) {
+            this.engine.write(encodedPackets[i], packet.options);
+          }
+        },
+        /**
+         * Clean up transport subscriptions and packet buffer.
+         *
+         * @private
+         */
+      },
+      {
+        key: "cleanup",
+        value: function cleanup() {
+          this.subs.forEach(function (subDestroy) {
+            return subDestroy();
+          });
+          this.subs.length = 0;
+          this.decoder.destroy();
+        },
+        /**
+         * Close the current socket.
+         *
+         * @private
+         */
+      },
+      {
+        key: "_close",
+        value: function _close() {
+          this.skipReconnect = true;
+          this._reconnecting = false;
+          this.onclose("forced close");
+          if (this.engine) this.engine.close();
+        },
+        /**
+         * Alias for close()
+         *
+         * @private
+         */
+      },
+      {
+        key: "disconnect",
+        value: function disconnect() {
+          return this._close();
+        },
+        /**
+         * Called upon engine close.
+         *
+         * @private
+         */
+      },
+      {
+        key: "onclose",
+        value: function onclose(reason, description) {
+          this.cleanup();
+          this.backoff.reset();
+          this._readyState = "closed";
+          this.emitReserved("close", reason, description);
+          if (this._reconnection && !this.skipReconnect) {
+            this.reconnect();
+          }
+        },
+        /**
+         * Attempt a reconnection.
+         *
+         * @private
+         */
+      },
+      {
+        key: "reconnect",
+        value: function reconnect() {
+          var _this4 = this;
+          if (this._reconnecting || this.skipReconnect) return this;
+          var self = this;
+          if (this.backoff.attempts >= this._reconnectionAttempts) {
+            this.backoff.reset();
+            this.emitReserved("reconnect_failed");
+            this._reconnecting = false;
+          } else {
+            var delay = this.backoff.duration();
+            this._reconnecting = true;
+            var timer = this.setTimeoutFn(function () {
+              if (self.skipReconnect) return;
+              _this4.emitReserved("reconnect_attempt", self.backoff.attempts);
+              // check again for the case socket closed in above events
+              if (self.skipReconnect) return;
+              self.open(function (err) {
+                if (err) {
+                  self._reconnecting = false;
+                  self.reconnect();
+                  _this4.emitReserved("reconnect_error", err);
+                } else {
+                  self.onreconnect();
+                }
+              });
+            }, delay);
+            if (this.opts.autoUnref) {
+              timer.unref();
+            }
+            this.subs.push(function () {
+              _this4.clearTimeoutFn(timer);
+            });
+          }
+        },
+        /**
+         * Called upon successful reconnect.
+         *
+         * @private
+         */
+      },
+      {
+        key: "onreconnect",
+        value: function onreconnect() {
+          var attempt = this.backoff.attempts;
+          this._reconnecting = false;
+          this.backoff.reset();
+          this.emitReserved("reconnect", attempt);
+        },
+      },
+    ]);
+    return Manager;
+  })(Emitter);
+
+  /**
+   * Managers cache.
+   */
+  var cache = {};
+  function lookup(uri, opts) {
+    if (_typeof(uri) === "object") {
+      opts = uri;
+      uri = undefined;
+    }
+    opts = opts || {};
+    var parsed = url(uri, opts.path || "/socket.io");
+    var source = parsed.source;
+    var id = parsed.id;
+    var path = parsed.path;
+    var sameNamespace = cache[id] && path in cache[id]["nsps"];
+    var newConnection =
+      opts.forceNew ||
+      opts["force new connection"] ||
+      false === opts.multiplex ||
+      sameNamespace;
+    var io;
+    if (newConnection) {
+      io = new Manager(source, opts);
+    } else {
+      if (!cache[id]) {
+        cache[id] = new Manager(source, opts);
+      }
+      io = cache[id];
+    }
+    if (parsed.query && !opts.query) {
+      opts.query = parsed.queryKey;
+    }
+    return io.socket(parsed.path, opts);
+  }
+  // so that "lookup" can be used both as a function (e.g. `io(...)`) and as a
+  // namespace (e.g. `io.connect(...)`), for backward compatibility
+  _extends(lookup, {
+    Manager: Manager,
+    Socket: Socket,
+    io: lookup,
+    connect: lookup,
+  });
+
+  return lookup;
+});
+//# sourceMappingURL=socket.io.js.map
diff --git a/demo_websockets/server.js b/demo_websockets/server.js
new file mode 100644
index 0000000000000000000000000000000000000000..b85aba493540602dbd9e6a2cfc8306f3cdac86b3
--- /dev/null
+++ b/demo_websockets/server.js
@@ -0,0 +1,129 @@
+let express = require("express");
+let http = require("http");
+let { Server } = require("socket.io");
+
+// after running
+// npm i socket.io
+// client library is at
+// node_modules/socket.io/client-dist/socket.io.js
+// copy that file into public/ so it can be "imported" client-side, too
+
+let app = express();
+let server = http.createServer(app);
+let io = new Server(server);
+
+app.use(express.static("public"));
+
+// global object to hold socket objects; structure is:
+// {[roomId]: {[socketId]: [...socket objects...]}}
+let rooms = {};
+// can also use socket.io's rooms functionality
+// instead of manually maintaining an object of rooms
+// https://socket.io/docs/v3/rooms/
+
+function generateRoomCode() {
+  let characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+  let result = "";
+  for (let i = 0; i < 4; i++) {
+    result += characters.charAt(Math.floor(Math.random() * characters.length));
+  }
+  return result;
+}
+
+// for debugging
+function printRooms() {
+  for (let [roomId, sockets] of Object.entries(rooms)) {
+    console.log(roomId);
+    for (let [socketId, socket] of Object.entries(sockets)) {
+      console.log(`\t${socketId}`);
+    }
+  }
+}
+
+app.post("/create", (req, res) => {
+  let roomId = generateRoomCode();
+  rooms[roomId] = {};
+  return res.json({ roomId });
+});
+
+app.get("/room/:roomId", (req, res) => {
+  let { roomId } = req.params;
+  if (!rooms.hasOwnProperty(roomId)) {
+    return res.status(404).send();
+  }
+  console.log("Sending room", roomId);
+  // could also use server-side rendering to create the HTML
+  // that way, we could embed the room code
+  // and existing chat messages in the generated HTML
+  // but the client can also get the roomId from the URL
+  // and use Ajax to request the messages on load
+  res.sendFile("public/room.html", { root: __dirname });
+});
+
+// if you need to do things like associate a socket with a logged in user, see
+// https://socket.io/how-to/deal-with-cookies
+// to see how you can fetch application cookies from the socket
+
+io.on("connection", (socket) => {
+  console.log(`Socket ${socket.id} connected`);
+
+  // extract room ID from URL
+  // could also send a separate registration event to register a socket to a room
+  // might want to do that ^ b/c not all browsers include referer, I think
+  let url = socket.handshake.headers.referer;
+  let pathParts = url.split("/");
+  let roomId = pathParts[pathParts.length - 1];
+  console.log(pathParts, roomId);
+
+  // room doesn't exist - this should never happen, but jic
+  if (!rooms.hasOwnProperty(roomId)) {
+    return;
+  }
+
+  // add socket object to room so other sockets in same room
+  // can send messages to it later
+  rooms[roomId][socket.id] = socket;
+
+  /* MUST REGISTER socket.on(event) listener FOR EVERY event CLIENT CAN SEND */
+
+  socket.on("disconnect", () => {
+    // disconnects are normal; close tab, refresh, browser freezes inactive tab, ...
+    // want to clean up global object, or else we'll have a memory leak
+    // WARNING: sockets don't always send disconnect events
+    // so you may want to periodically clean up your room object for old socket ids
+    console.log(`Socket ${socket.id} disconnected`);
+    delete rooms[roomId][socket.id];
+  });
+
+  socket.on("foo", ({ message }) => {
+    // we still have a reference to the roomId defined above
+    // b/c this function is defined inside the outer function
+    console.log(`Socket ${socket.id} sent message: ${message}, ${roomId}`);
+    console.log("Broadcasting message to other sockets");
+
+    // this would send the message to all other sockets
+    // but we want to only send it to other sockets in this room
+    // socket.broadcast.emit("message", message);
+
+    for (let otherSocket of Object.values(rooms[roomId])) {
+      // don't need to send same message back to socket
+      // socket.broadcast.emit automatically skips current socket
+      // but since we're doing this manually, we need to do it ourselves
+      if (otherSocket.id === socket.id) {
+        continue;
+      }
+      console.log(`Sending message ${message} to socket ${otherSocket.id}`);
+      otherSocket.emit("bar", message);
+    }
+  });
+
+  socket.on("hello", (data) => {
+    console.log(data);
+  });
+});
+
+let host = "localhost";
+let port = 3000;
+server.listen(port, host, () => {
+  console.log(`http://${host}:${port}`);
+});