Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
server.py664 linesDownload Raw Back to socketio
1import logging2 3import engineio4 5from . import base_server6from . import exceptions7from . import packet8 9default_logger = logging.getLogger('socketio.server')10 11 12class Server(base_server.BaseServer):13    """A Socket.IO server.14 15    This class implements a fully compliant Socket.IO web server with support16    for websocket and long-polling transports.17 18    :param client_manager: The client manager instance that will manage the19                           client list. When this is omitted, the client list20                           is stored in an in-memory structure, so the use of21                           multiple connected servers is not possible.22    :param logger: To enable logging set to ``True`` or pass a logger object to23                   use. To disable logging set to ``False``. The default is24                   ``False``. Note that fatal errors are logged even when25                   ``logger`` is ``False``.26    :param serializer: The serialization method to use when transmitting27                       packets. Valid values are ``'default'``, ``'pickle'``,28                       ``'msgpack'`` and ``'cbor'``. Alternatively, a subclass29                       of the :class:`Packet` class with custom implementations30                       of the ``encode()`` and ``decode()`` methods can be31                       provided. Client and server must use compatible32                       serializers.33    :param json: An alternative json module to use for encoding and decoding34                 packets. Custom json modules must have ``dumps`` and ``loads``35                 functions that are compatible with the standard library36                 versions.37    :param async_handlers: If set to ``True``, event handlers for a client are38                           executed in separate threads. To run handlers for a39                           client synchronously, set to ``False``. The default40                           is ``True``.41    :param always_connect: When set to ``False``, new connections are42                           provisory until the connect handler returns43                           something other than ``False``, at which point they44                           are accepted. When set to ``True``, connections are45                           immediately accepted, and then if the connect46                           handler returns ``False`` a disconnect is issued.47                           Set to ``True`` if you need to emit events from the48                           connect handler and your client is confused when it49                           receives events before the connection acceptance.50                           In any other case use the default of ``False``.51    :param namespaces: a list of namespaces that are accepted, in addition to52                       any namespaces for which handlers have been defined. The53                       default is `['/']`, which always accepts connections to54                       the default namespace. Set to `'*'` to accept all55                       namespaces.56    :param kwargs: Connection parameters for the underlying Engine.IO server.57 58    The Engine.IO configuration supports the following settings:59 60    :param async_mode: The asynchronous model to use. See the Deployment61                       section in the documentation for a description of the62                       available options. Valid async modes are63                       ``'threading'``, ``'eventlet'``, ``'gevent'`` and64                       ``'gevent_uwsgi'``. If this argument is not given,65                       ``'eventlet'`` is tried first, then ``'gevent_uwsgi'``,66                       then ``'gevent'``, and finally ``'threading'``.67                       The first async mode that has all its dependencies68                       installed is then one that is chosen.69    :param ping_interval: The interval in seconds at which the server pings70                          the client. The default is 25 seconds. For advanced71                          control, a two element tuple can be given, where72                          the first number is the ping interval and the second73                          is a grace period added by the server.74    :param ping_timeout: The time in seconds that the client waits for the75                         server to respond before disconnecting. The default76                         is 20 seconds.77    :param max_http_buffer_size: The maximum size that is accepted for incoming78                                 messages.  The default is 1,000,000 bytes. In79                                 spite of its name, the value set in this80                                 argument is enforced for HTTP long-polling and81                                 WebSocket connections.82    :param allow_upgrades: Whether to allow transport upgrades or not. The83                           default is ``True``.84    :param http_compression: Whether to compress packages when using the85                             polling transport. The default is ``True``.86    :param compression_threshold: Only compress messages when their byte size87                                  is greater than this value. The default is88                                  1024 bytes.89    :param cookie: If set to a string, it is the name of the HTTP cookie the90                   server sends back tot he client containing the client91                   session id. If set to a dictionary, the ``'name'`` key92                   contains the cookie name and other keys define cookie93                   attributes, where the value of each attribute can be a94                   string, a callable with no arguments, or a boolean. If set95                   to ``None`` (the default), a cookie is not sent to the96                   client.97    :param cors_allowed_origins: Origin or list of origins that are allowed to98                                 connect to this server. Only the same origin99                                 is allowed by default. Set this argument to100                                 ``'*'`` to allow all origins, or to ``[]`` to101                                 disable CORS handling.102    :param cors_credentials: Whether credentials (cookies, authentication) are103                             allowed in requests to this server. The default is104                             ``True``.105    :param monitor_clients: If set to ``True``, a background task will ensure106                            inactive clients are closed. Set to ``False`` to107                            disable the monitoring task (not recommended). The108                            default is ``True``.109    :param engineio_logger: To enable Engine.IO logging set to ``True`` or pass110                            a logger object to use. To disable logging set to111                            ``False``. The default is ``False``. Note that112                            fatal errors are logged even when113                            ``engineio_logger`` is ``False``.114    """115    def emit(self, event, data=None, to=None, room=None, skip_sid=None,116             namespace=None, callback=None, ignore_queue=False):117        """Emit a custom event to one or more connected clients.118 119        :param event: The event name. It can be any string. The event names120                      ``'connect'``, ``'message'`` and ``'disconnect'`` are121                      reserved and should not be used.122        :param data: The data to send to the client or clients. Data can be of123                     type ``str``, ``bytes``, ``list`` or ``dict``. To send124                     multiple arguments, use a tuple where each element is of125                     one of the types indicated above.126        :param to: The recipient of the message. This can be set to the127                   session ID of a client to address only that client, to any128                   custom room created by the application to address all129                   the clients in that room, or to a list of custom room130                   names. If this argument is omitted the event is broadcasted131                   to all connected clients.132        :param room: Alias for the ``to`` parameter.133        :param skip_sid: The session ID of a client to skip when broadcasting134                         to a room or to all clients. This can be used to135                         prevent a message from being sent to the sender. To136                         skip multiple sids, pass a list.137        :param namespace: The Socket.IO namespace for the event. If this138                          argument is omitted the event is emitted to the139                          default namespace.140        :param callback: If given, this function will be called to acknowledge141                         the client has received the message. The arguments142                         that will be passed to the function are those provided143                         by the client. Callback functions can only be used144                         when addressing an individual client.145        :param ignore_queue: Only used when a message queue is configured. If146                             set to ``True``, the event is emitted to the147                             clients directly, without going through the queue.148                             This is more efficient, but only works when a149                             single server process is used. It is recommended150                             to always leave this parameter with its default151                             value of ``False``.152 153        Note: this method is not thread safe. If multiple threads are emitting154        at the same time to the same client, then messages composed of155        multiple packets may end up being sent in an incorrect sequence. Use156        standard concurrency solutions (such as a Lock object) to prevent this157        situation.158        """159        namespace = namespace or '/'160        room = to or room161        self.logger.info('emitting event "%s" to %s [%s]', event,162                         room or 'all', namespace)163        self.manager.emit(event, data, namespace, room=room,164                          skip_sid=skip_sid, callback=callback,165                          ignore_queue=ignore_queue)166 167    def send(self, data, to=None, room=None, skip_sid=None, namespace=None,168             callback=None, ignore_queue=False):169        """Send a message to one or more connected clients.170 171        This function emits an event with the name ``'message'``. Use172        :func:`emit` to issue custom event names.173 174        :param data: The data to send to the client or clients. Data can be of175                     type ``str``, ``bytes``, ``list`` or ``dict``. To send176                     multiple arguments, use a tuple where each element is of177                     one of the types indicated above.178        :param to: The recipient of the message. This can be set to the179                   session ID of a client to address only that client, to any180                   any custom room created by the application to address all181                   the clients in that room, or to a list of custom room182                   names. If this argument is omitted the event is broadcasted183                   to all connected clients.184        :param room: Alias for the ``to`` parameter.185        :param skip_sid: The session ID of a client to skip when broadcasting186                         to a room or to all clients. This can be used to187                         prevent a message from being sent to the sender. To188                         skip multiple sids, pass a list.189        :param namespace: The Socket.IO namespace for the event. If this190                          argument is omitted the event is emitted to the191                          default namespace.192        :param callback: If given, this function will be called to acknowledge193                         the client has received the message. The arguments194                         that will be passed to the function are those provided195                         by the client. Callback functions can only be used196                         when addressing an individual client.197        :param ignore_queue: Only used when a message queue is configured. If198                             set to ``True``, the event is emitted to the199                             clients directly, without going through the queue.200                             This is more efficient, but only works when a201                             single server process is used. It is recommended202                             to always leave this parameter with its default203                             value of ``False``.204        """205        self.emit('message', data=data, to=to, room=room, skip_sid=skip_sid,206                  namespace=namespace, callback=callback,207                  ignore_queue=ignore_queue)208 209    def call(self, event, data=None, to=None, sid=None, namespace=None,210             timeout=60, ignore_queue=False):211        """Emit a custom event to a client and wait for the response.212 213        This method issues an emit with a callback and waits for the callback214        to be invoked before returning. If the callback isn't invoked before215        the timeout, then a ``TimeoutError`` exception is raised. If the216        Socket.IO connection drops during the wait, this method still waits217        until the specified timeout.218 219        :param event: The event name. It can be any string. The event names220                      ``'connect'``, ``'message'`` and ``'disconnect'`` are221                      reserved and should not be used.222        :param data: The data to send to the client or clients. Data can be of223                     type ``str``, ``bytes``, ``list`` or ``dict``. To send224                     multiple arguments, use a tuple where each element is of225                     one of the types indicated above.226        :param to: The session ID of the recipient client.227        :param sid: Alias for the ``to`` parameter.228        :param namespace: The Socket.IO namespace for the event. If this229                          argument is omitted the event is emitted to the230                          default namespace.231        :param timeout: The waiting timeout. If the timeout is reached before232                        the client acknowledges the event, then a233                        ``TimeoutError`` exception is raised.234        :param ignore_queue: Only used when a message queue is configured. If235                             set to ``True``, the event is emitted to the236                             client directly, without going through the queue.237                             This is more efficient, but only works when a238                             single server process is used. It is recommended239                             to always leave this parameter with its default240                             value of ``False``.241 242        Note: this method is not thread safe. If multiple threads are emitting243        at the same time to the same client, then messages composed of244        multiple packets may end up being sent in an incorrect sequence. Use245        standard concurrency solutions (such as a Lock object) to prevent this246        situation.247        """248        if to is None and sid is None:249            raise ValueError('Cannot use call() to broadcast.')250        if not self.async_handlers:251            raise RuntimeError(252                'Cannot use call() when async_handlers is False.')253        callback_event = self.eio.create_event()254        callback_args = []255 256        def event_callback(*args):257            callback_args.append(args)258            callback_event.set()259 260        self.emit(event, data=data, room=to or sid, namespace=namespace,261                  callback=event_callback, ignore_queue=ignore_queue)262        if not callback_event.wait(timeout=timeout):263            raise exceptions.TimeoutError()264        return callback_args[0] if len(callback_args[0]) > 1 \265            else callback_args[0][0] if len(callback_args[0]) == 1 \266            else None267 268    def enter_room(self, sid, room, namespace=None):269        """Enter a room.270 271        This function adds the client to a room. The :func:`emit` and272        :func:`send` functions can optionally broadcast events to all the273        clients in a room.274 275        :param sid: Session ID of the client.276        :param room: Room name. If the room does not exist it is created.277        :param namespace: The Socket.IO namespace for the event. If this278                          argument is omitted the default namespace is used.279        """280        namespace = namespace or '/'281        self.logger.info('%s is entering room %s [%s]', sid, room, namespace)282        self.manager.enter_room(sid, namespace, room)283 284    def leave_room(self, sid, room, namespace=None):285        """Leave a room.286 287        This function removes the client from a room.288 289        :param sid: Session ID of the client.290        :param room: Room name.291        :param namespace: The Socket.IO namespace for the event. If this292                          argument is omitted the default namespace is used.293        """294        namespace = namespace or '/'295        self.logger.info('%s is leaving room %s [%s]', sid, room, namespace)296        self.manager.leave_room(sid, namespace, room)297 298    def close_room(self, room, namespace=None):299        """Close a room.300 301        This function removes all the clients from the given room.302 303        :param room: Room name.304        :param namespace: The Socket.IO namespace for the event. If this305                          argument is omitted the default namespace is used.306        """307        namespace = namespace or '/'308        self.logger.info('room %s is closing [%s]', room, namespace)309        self.manager.close_room(room, namespace)310 311    def get_session(self, sid, namespace=None):312        """Return the user session for a client.313 314        :param sid: The session id of the client.315        :param namespace: The Socket.IO namespace. If this argument is omitted316                          the default namespace is used.317 318        The return value is a dictionary. Modifications made to this319        dictionary are not guaranteed to be preserved unless320        ``save_session()`` is called, or when the ``session`` context manager321        is used.322        """323        namespace = namespace or '/'324        eio_sid = self.manager.eio_sid_from_sid(sid, namespace)325        eio_session = self.eio.get_session(eio_sid)326        return eio_session.setdefault(namespace, {})327 328    def save_session(self, sid, session, namespace=None):329        """Store the user session for a client.330 331        :param sid: The session id of the client.332        :param session: The session dictionary.333        :param namespace: The Socket.IO namespace. If this argument is omitted334                          the default namespace is used.335        """336        namespace = namespace or '/'337        eio_sid = self.manager.eio_sid_from_sid(sid, namespace)338        eio_session = self.eio.get_session(eio_sid)339        eio_session[namespace] = session340 341    def session(self, sid, namespace=None):342        """Return the user session for a client with context manager syntax.343 344        :param sid: The session id of the client.345 346        This is a context manager that returns the user session dictionary for347        the client. Any changes that are made to this dictionary inside the348        context manager block are saved back to the session. Example usage::349 350            @sio.on('connect')351            def on_connect(sid, environ):352                username = authenticate_user(environ)353                if not username:354                    return False355                with sio.session(sid) as session:356                    session['username'] = username357 358            @sio.on('message')359            def on_message(sid, msg):360                with sio.session(sid) as session:361                    print('received message from ', session['username'])362        """363        class _session_context_manager(object):364            def __init__(self, server, sid, namespace):365                self.server = server366                self.sid = sid367                self.namespace = namespace368                self.session = None369 370            def __enter__(self):371                self.session = self.server.get_session(sid,372                                                       namespace=namespace)373                return self.session374 375            def __exit__(self, *args):376                self.server.save_session(sid, self.session,377                                         namespace=namespace)378 379        return _session_context_manager(self, sid, namespace)380 381    def disconnect(self, sid, namespace=None, ignore_queue=False):382        """Disconnect a client.383 384        :param sid: Session ID of the client.385        :param namespace: The Socket.IO namespace to disconnect. If this386                          argument is omitted the default namespace is used.387        :param ignore_queue: Only used when a message queue is configured. If388                             set to ``True``, the disconnect is processed389                             locally, without broadcasting on the queue. It is390                             recommended to always leave this parameter with391                             its default value of ``False``.392        """393        namespace = namespace or '/'394        if ignore_queue:395            delete_it = self.manager.is_connected(sid, namespace)396        else:397            delete_it = self.manager.can_disconnect(sid, namespace)398        if delete_it:399            self.logger.info('Disconnecting %s [%s]', sid, namespace)400            eio_sid = self.manager.pre_disconnect(sid, namespace=namespace)401            self._send_packet(eio_sid, self.packet_class(402                packet.DISCONNECT, namespace=namespace))403            self._trigger_event('disconnect', namespace, sid)404            self.manager.disconnect(sid, namespace=namespace,405                                    ignore_queue=True)406 407    def shutdown(self):408        """Stop Socket.IO background tasks.409 410        This method stops all background activity initiated by the Socket.IO411        server. It must be called before shutting down the web server.412        """413        self.logger.info('Socket.IO is shutting down')414        self.eio.shutdown()415 416    def handle_request(self, environ, start_response):417        """Handle an HTTP request from the client.418 419        This is the entry point of the Socket.IO application, using the same420        interface as a WSGI application. For the typical usage, this function421        is invoked by the :class:`Middleware` instance, but it can be invoked422        directly when the middleware is not used.423 424        :param environ: The WSGI environment.425        :param start_response: The WSGI ``start_response`` function.426 427        This function returns the HTTP response body to deliver to the client428        as a byte sequence.429        """430        return self.eio.handle_request(environ, start_response)431 432    def start_background_task(self, target, *args, **kwargs):433        """Start a background task using the appropriate async model.434 435        This is a utility function that applications can use to start a436        background task using the method that is compatible with the437        selected async mode.438 439        :param target: the target function to execute.440        :param args: arguments to pass to the function.441        :param kwargs: keyword arguments to pass to the function.442 443        This function returns an object that represents the background task,444        on which the ``join()`` methond can be invoked to wait for the task to445        complete.446        """447        return self.eio.start_background_task(target, *args, **kwargs)448 449    def sleep(self, seconds=0):450        """Sleep for the requested amount of time using the appropriate async451        model.452 453        This is a utility function that applications can use to put a task to454        sleep without having to worry about using the correct call for the455        selected async mode.456        """457        return self.eio.sleep(seconds)458 459    def instrument(self, auth=None, mode='development', read_only=False,460                   server_id=None, namespace='/admin',461                   server_stats_interval=2):462        """Instrument the Socket.IO server for monitoring with the `Socket.IO463        Admin UI <https://socket.io/docs/v4/admin-ui/>`_.464 465        :param auth: Authentication credentials for Admin UI access. Set to a466                     dictionary with the expected login (usually ``username``467                     and ``password``) or a list of dictionaries if more than468                     one set of credentials need to be available. For more469                     complex authentication methods, set to a callable that470                     receives the authentication dictionary as an argument and471                     returns ``True`` if the user is allowed or ``False``472                     otherwise. To disable authentication, set this argument to473                     ``False`` (not recommended, never do this on a production474                     server).475        :param mode: The reporting mode. The default is ``'development'``,476                     which is best used while debugging, as it may have a477                     significant performance effect. Set to ``'production'`` to478                     reduce the amount of information that is reported to the479                     admin UI.480        :param read_only: If set to ``True``, the admin interface will be481                          read-only, with no option to modify room assignments482                          or disconnect clients. The default is ``False``.483        :param server_id: The server name to use for this server. If this484                          argument is omitted, the server generates its own485                          name.486        :param namespace: The Socket.IO namespace to use for the admin487                          interface. The default is ``/admin``.488        :param server_stats_interval: The interval in seconds at which the489                                      server emits a summary of it stats to all490                                      connected admins.491        """492        from .admin import InstrumentedServer493        return InstrumentedServer(494            self, auth=auth, mode=mode, read_only=read_only,495            server_id=server_id, namespace=namespace,496            server_stats_interval=server_stats_interval)497 498    def _send_packet(self, eio_sid, pkt):499        """Send a Socket.IO packet to a client."""500        encoded_packet = pkt.encode()501        if isinstance(encoded_packet, list):502            for ep in encoded_packet:503                self.eio.send(eio_sid, ep)504        else:505            self.eio.send(eio_sid, encoded_packet)506 507    def _send_eio_packet(self, eio_sid, eio_pkt):508        """Send a raw Engine.IO packet to a client."""509        self.eio.send_packet(eio_sid, eio_pkt)510 511    def _handle_connect(self, eio_sid, namespace, data):512        """Handle a client connection request."""513        namespace = namespace or '/'514        sid = None515        if namespace in self.handlers or namespace in self.namespace_handlers \516                or self.namespaces == '*' or namespace in self.namespaces:517            sid = self.manager.connect(eio_sid, namespace)518        if sid is None:519            self._send_packet(eio_sid, self.packet_class(520                packet.CONNECT_ERROR, data='Unable to connect',521                namespace=namespace))522            return523 524        if self.always_connect:525            self._send_packet(eio_sid, self.packet_class(526                packet.CONNECT, {'sid': sid}, namespace=namespace))527        fail_reason = exceptions.ConnectionRefusedError().error_args528        try:529            if data:530                success = self._trigger_event(531                    'connect', namespace, sid, self.environ[eio_sid], data)532            else:533                try:534                    success = self._trigger_event(535                        'connect', namespace, sid, self.environ[eio_sid])536                except TypeError:537                    success = self._trigger_event(538                        'connect', namespace, sid, self.environ[eio_sid], None)539        except exceptions.ConnectionRefusedError as exc:540            fail_reason = exc.error_args541            success = False542 543        if success is False:544            if self.always_connect:545                self.manager.pre_disconnect(sid, namespace)546                self._send_packet(eio_sid, self.packet_class(547                    packet.DISCONNECT, data=fail_reason, namespace=namespace))548            else:549                self._send_packet(eio_sid, self.packet_class(550                    packet.CONNECT_ERROR, data=fail_reason,551                    namespace=namespace))552            self.manager.disconnect(sid, namespace, ignore_queue=True)553        elif not self.always_connect:554            self._send_packet(eio_sid, self.packet_class(555                packet.CONNECT, {'sid': sid}, namespace=namespace))556 557    def _handle_disconnect(self, eio_sid, namespace):558        """Handle a client disconnect."""559        namespace = namespace or '/'560        sid = self.manager.sid_from_eio_sid(eio_sid, namespace)561        if not self.manager.is_connected(sid, namespace):  # pragma: no cover562            return563        self.manager.pre_disconnect(sid, namespace=namespace)564        self._trigger_event('disconnect', namespace, sid)565        self.manager.disconnect(sid, namespace, ignore_queue=True)566 567    def _handle_event(self, eio_sid, namespace, id, data):568        """Handle an incoming client event."""569        namespace = namespace or '/'570        sid = self.manager.sid_from_eio_sid(eio_sid, namespace)571        self.logger.info('received event "%s" from %s [%s]', data[0], sid,572                         namespace)573        if not self.manager.is_connected(sid, namespace):574            self.logger.warning('%s is not connected to namespace %s',575                                sid, namespace)576            return577        if self.async_handlers:578            self.start_background_task(self._handle_event_internal, self, sid,579                                       eio_sid, data, namespace, id)580        else:581            self._handle_event_internal(self, sid, eio_sid, data, namespace,582                                        id)583 584    def _handle_event_internal(self, server, sid, eio_sid, data, namespace,585                               id):586        r = server._trigger_event(data[0], namespace, sid, *data[1:])587        if r != self.not_handled and id is not None:588            # send ACK packet with the response returned by the handler589            # tuples are expanded as multiple arguments590            if r is None:591                data = []592            elif isinstance(r, tuple):593                data = list(r)594            else:595                data = [r]596            server._send_packet(eio_sid, self.packet_class(597                packet.ACK, namespace=namespace, id=id, data=data))598 599    def _handle_ack(self, eio_sid, namespace, id, data):600        """Handle ACK packets from the client."""601        namespace = namespace or '/'602        sid = self.manager.sid_from_eio_sid(eio_sid, namespace)603        self.logger.info('received ack from %s [%s]', sid, namespace)604        self.manager.trigger_callback(sid, id, data)605 606    def _trigger_event(self, event, namespace, *args):607        """Invoke an application event handler."""608        # first see if we have an explicit handler for the event609        handler, args = self._get_event_handler(event, namespace, args)610        if handler:611            return handler(*args)612        # or else, forward the event to a namespace handler if one exists613        handler, args = self._get_namespace_handler(namespace, args)614        if handler:615            return handler.trigger_event(event, *args)616        else:617            return self.not_handled618 619    def _handle_eio_connect(self, eio_sid, environ):620        """Handle the Engine.IO connection event."""621        if not self.manager_initialized:622            self.manager_initialized = True623            self.manager.initialize()624        self.environ[eio_sid] = environ625 626    def _handle_eio_message(self, eio_sid, data):627        """Dispatch Engine.IO messages."""628        if eio_sid in self._binary_packet:629            pkt = self._binary_packet[eio_sid]630            if pkt.add_attachment(data):631                del self._binary_packet[eio_sid]632                if pkt.packet_type == packet.BINARY_EVENT:633                    self._handle_event(eio_sid, pkt.namespace, pkt.id,634                                       pkt.data)635                else:636                    self._handle_ack(eio_sid, pkt.namespace, pkt.id, pkt.data)637        else:638            pkt = self.packet_class(encoded_packet=data)639            if pkt.packet_type == packet.CONNECT:640                self._handle_connect(eio_sid, pkt.namespace, pkt.data)641            elif pkt.packet_type == packet.DISCONNECT:642                self._handle_disconnect(eio_sid, pkt.namespace)643            elif pkt.packet_type == packet.EVENT:644                self._handle_event(eio_sid, pkt.namespace, pkt.id, pkt.data)645            elif pkt.packet_type == packet.ACK:646                self._handle_ack(eio_sid, pkt.namespace, pkt.id, pkt.data)647            elif pkt.packet_type == packet.BINARY_EVENT or \648                    pkt.packet_type == packet.BINARY_ACK:649                self._binary_packet[eio_sid] = pkt650            elif pkt.packet_type == packet.CONNECT_ERROR:651                raise ValueError('Unexpected CONNECT_ERROR packet.')652            else:653                raise ValueError('Unknown packet type.')654 655    def _handle_eio_disconnect(self, eio_sid):656        """Handle Engine.IO disconnect event."""657        for n in list(self.manager.get_namespaces()).copy():658            self._handle_disconnect(eio_sid, n)659        if eio_sid in self.environ:660            del self.environ[eio_sid]661 662    def _engineio_server_class(self):663        return engineio.Server664 
codekingpro/portable-devtools · Team Ai