From 4aaa43607915378cf07b2677ac9bc05cad8156f8 Mon Sep 17 00:00:00 2001 From: Fasterino Date: Mon, 20 Oct 2025 21:08:52 +0300 Subject: [PATCH] init --- .DS_Store | Bin 0 -> 6148 bytes .gitignore | 3 + Dockerfile | 0 back/__init__.py | 19 + back/create_room.py | 108 + back/game.py | 231 ++ back/socket.py | 123 + docker-compose.yml | 10 + front/__init__.py | 16 + jopa.py | 0 main.py | 23 + requirements.txt | 3 + start.sh | 1 + static/colorpicker.iife.min.js | 1 + static/colorpicker.min.css | 1 + static/game.js | 226 ++ static/index.html | 166 + static/main.js | 208 ++ static/socket.io.js | 5523 ++++++++++++++++++++++++++++++++ static/style.css | 162 + 20 files changed, 6824 insertions(+) create mode 100644 .DS_Store create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 back/__init__.py create mode 100644 back/create_room.py create mode 100644 back/game.py create mode 100644 back/socket.py create mode 100644 docker-compose.yml create mode 100644 front/__init__.py create mode 100644 jopa.py create mode 100644 main.py create mode 100644 requirements.txt create mode 100755 start.sh create mode 100644 static/colorpicker.iife.min.js create mode 100644 static/colorpicker.min.css create mode 100644 static/game.js create mode 100644 static/index.html create mode 100644 static/main.js create mode 100644 static/socket.io.js create mode 100644 static/style.css diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5bfc8adab31bf8831570de03e7cf68385fabacef GIT binary patch literal 6148 zcmeHK!H&}~5FNMOO`t;R0i-=faIH$&U0@NHs4NGr8w3YHp=k>Zo5m$cDX6NHGu-$T zPJ9U;z>mNi+Z7~TII=6`k;ZRgzp*E;vfU7o7%axOh&n{%!Wmna(VSsC&c0<0)5Cc} z%`u`Ojc7^}3b)v{!zy4ExM&LS-fd&IhBT)S*t@^>Z}=#YqbQLP7<@bo;JqKp5aGC> zNU=|Aye~E04!xp`B&AeTnc{kl>ChYS=9sm|)zsLZx7afR+RI z66Zy~|4lTSt({%Rb)2^IBACi$Pz;MjJ{V@t`025fNwkTF(UW+x3_DjJ$g~*7=_FGL z@puBt^QUn-mdk-$q~l!W2717~623;;S_P~ERRx;*vBUTO!#{ujSCj0MRlq9nUnwA3$HDOc zuVn7lt(W7w)`P!sSSR74N~7Va($I=o>6Gq6cRG2q+nBV-@(P3j73L Cm9q8# literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9eaf7a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.venv +__pycache__ +.vscode \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/back/__init__.py b/back/__init__.py new file mode 100644 index 0000000..5a1c2ba --- /dev/null +++ b/back/__init__.py @@ -0,0 +1,19 @@ +from fastapi import APIRouter +from socketio import AsyncServer + +from .socket import Socket +from .game import game_router +from .create_room import create_room_router + + +def get_backend() -> tuple[APIRouter, AsyncServer]: + router = APIRouter(prefix="/api") + sio = AsyncServer(async_mode="asgi") + server = Socket(sio) + + @sio.on("connect") # type: ignore + async def sio_connect(sid, _): + socket = server.to(sid) + await create_room_router(server, socket) + + return router, sio diff --git a/back/create_room.py b/back/create_room.py new file mode 100644 index 0000000..3df8d36 --- /dev/null +++ b/back/create_room.py @@ -0,0 +1,108 @@ +from random import randint + +from .socket import Socket +from .game import game_router + + +async def create_room_router(server: Socket, socket: Socket): + async def check_nickname(nickname: str) -> str | None: + nickname = nickname.strip() + if len(nickname) < 4: + await socket.emit("room-error", "Никнейм должен быть больше 4 букв!") + return + + return nickname + + async def check_width(width: str) -> int | None: + width = width.strip() + if not width.isnumeric(): + await socket.emit("room-error", "Ширина должна быть числом!") + return + + width_i = int(width) + + if width_i < 5 or width_i > 25: + await socket.emit("room-error", "Ширина может быть числом от 5 до 25!") + return + + return width_i + + async def check_height(height: str) -> int | None: + height = height.strip() + if not height.isnumeric(): + await socket.emit("room-error", "Высота должна быть числом!") + return + + height_i = int(height) + + if height_i < 5 or height_i > 15: + await socket.emit("room-error", "Высота может быть числом от 5 до 15!") + return + + return height_i + + async def check_bombs(bombs: str, width: int, height: int) -> int | None: + bombs = bombs.strip() + if not bombs.isnumeric(): + await socket.emit("room-error", "Кол-во бомб должно быть числом!") + return + + bombs_i = int(bombs) + bombs_max = (width * height * 2) // 5 + + if bombs_i < 5 or bombs_i > bombs_max: + await socket.emit( + "room-error", f"Кол-во бомб может быть от 5 до {bombs_max}!" + ) + return + + return bombs_i + + async def check_room(room_id: str) -> str | None: + room_id = room_id.strip() + + if not room_id.isnumeric() or len(room_id) != 6: + await socket.emit("room-error", "Код должен быть из 6 цифр!") + return + + if not server.room_exists(room_id): + await socket.emit("room-error", "Такой комнаты не существует!") + return + + return room_id + + @socket.on("create-room") + async def create_room(_nickname: str, _width: str, _height: str, _bombs: str): + nickname = await check_nickname(_nickname) + if nickname is None: + return + + width = await check_width(_width) + if width is None: + return + + height = await check_height(_height) + if height is None: + return + + bombs = await check_bombs(_bombs, width, height) + if bombs is None: + return + + room_id = str(randint(100000, 999999)) + + room = await socket.join(room_id) + await game_router(server, socket, room, nickname, width, height, bombs) + + @socket.on("join-room") + async def join_room(_nickname: str, _room_id: str): + nickname = await check_nickname(_nickname) + if nickname is None: + return + + room_id = await check_room(_room_id) + if room_id is None: + return + + room = await socket.join(room_id) + await game_router(server, socket, room, nickname) diff --git a/back/game.py b/back/game.py new file mode 100644 index 0000000..520acd4 --- /dev/null +++ b/back/game.py @@ -0,0 +1,231 @@ +from dataclasses import dataclass, field +from types import SimpleNamespace +from random import randint + +from .socket import Socket + + +class CellState: + OPEN = 0 + CLOSED = 1 + FLAGGED = 2 + MARKED = 3 + NOT_A_FIELD = 4 + + +STR_CLOSED = "" +STR_BOMB = "💣" +STR_FLAGGED = "🚩" +STR_MARKED = "❓" +STR_NUMBERS = [" ", *(str(i) for i in range(1, 9))] + + +@dataclass +class GameData: + width: int + height: int + bombs: int + game_over: bool = False + win: bool = True + bombs_in_field: list[tuple[int, int]] = field(default_factory=list) + field: list[list[int]] = field(default_factory=list) + + def generate(self) -> "GameData": + self.game_over = False + self.win + + self.field.clear() + self.bombs_in_field.clear() + + for _ in range(self.width): + self.field.append([CellState.CLOSED for _ in range(self.height)]) + + return self + + def generate_bombs(self, ex: int, ey: int): + while len(self.bombs_in_field) < self.bombs: + x = randint(0, self.width - 1) + y = randint(0, self.height - 1) + if x == ex and y == ey: + continue + + skip = False + for x1, y1 in self.bombs_in_field: + if x1 == x and y1 == y: + skip = True + break + + if skip: + continue + + self.bombs_in_field.append((x, y)) + + def is_bomb(self, x: int, y: int) -> bool: + for x1, y1 in self.bombs_in_field: + if x1 == x and y1 == y: + return True + return False + + def get_number(self, x: int, y: int) -> int: + bombs = 0 + + for i in range(-1, 2): + for j in range(-1, 2): + if i == 0 and j == 0: + continue + if self.is_bomb(x + i, y + j): + bombs += 1 + + return bombs + + def is_win(self) -> bool: + if len(self.bombs_in_field) == 0: + return False + + must_be_open = self.width * self.height - self.bombs + flaged_cells = 0 + open_cells = 0 + + for x in range(self.width): + for y in range(self.height): + if self.field[x][y] == CellState.FLAGGED and self.is_bomb(x, y): + flaged_cells += 1 + if self.field[x][y] == CellState.OPEN: + open_cells += 1 + + if open_cells == must_be_open or self.bombs == flaged_cells: + return True + + return False + + +async def game_router( + server: Socket, + socket: Socket, + room: Socket, + nickname: str, + width=0, + height=0, + bombs=0, +): + async def emit_all_in_room(event: str, *args): + await socket.emit(event, *args) + await room.emit(event, *args) + + async def update_field(): + str_field = [] + flags = 0 + + for x, row in enumerate(data.field): + str_row = [] + for y, cell in enumerate(row): + if ( + data.game_over + and (not data.win or cell != CellState.FLAGGED) + and data.is_bomb(x, y) + ): + str_row.append(STR_BOMB) + else: + match cell: + case CellState.CLOSED: + str_row.append(STR_CLOSED) + case CellState.FLAGGED: + str_row.append(STR_FLAGGED) + flags += 1 + case CellState.MARKED: + str_row.append(STR_MARKED) + case _: + str_row.append(STR_NUMBERS[data.get_number(x, y)]) + + str_field.append(str_row) + + if data.game_over: + text = "Победа!" if data.win else "Поражение!" + else: + text = "" + await emit_all_in_room("update-field", str_field, flags, data.bombs, text) + + is_host = width > 0 and height > 0 and bombs > 0 + room_id = room.sid + data = room.room_data(lambda: GameData(width, height, bombs).generate()) + + @socket.on("chat") + async def chat(msg: str): + await room.emit("chat", nickname, msg) + + @socket.on("disconnect") + async def disconnect(_reason): + if is_host: + await room.emit("reload-page") + else: + await room.emit("other-disconnect", socket.sid, nickname) + + @socket.on("about-me") + async def about_me(): + await room.emit("about-me", socket.sid, nickname + (" 👑" if is_host else "")) + + @socket.on("cursor") + async def cursor(x: float, y: float): + await room.emit("cursor", socket.sid, nickname, x, y) + + @socket.on("restart") + async def restart(): + data.generate() + await update_field() + + def openCells(x: int, y: int): + if ( + x < 0 + or y < 0 + or x >= data.width + or y >= data.height + or data.field[x][y] == CellState.OPEN + ): + return + + data.field[x][y] = CellState.OPEN + + if data.get_number(x, y) == 0: + for i in range(-1, 2): + for j in range(-1, 2): + openCells(x + i, y + j) + + @socket.on("click") + async def click(left: bool, x: int, y: int): + if data.game_over or x < 0 or y < 0 or x >= data.width or y >= data.height: + return + + cell = data.field[x][y] + + if cell == CellState.OPEN: + return + + if left: + if cell != CellState.CLOSED: + return + if data.is_bomb(x, y): + data.game_over = True + data.field[x][y] = CellState.OPEN + else: + if len(data.bombs_in_field) == 0: + data.generate_bombs(x, y) + openCells(x, y) + data.win = data.is_win() + if data.win: + data.game_over = True + + else: + cell += 1 + if cell == CellState.NOT_A_FIELD: + cell = CellState.CLOSED + data.field[x][y] = cell + if cell == CellState.FLAGGED: + data.win = data.is_win() + if data.win: + data.game_over = True + + await update_field() + + await socket.emit("room-join", room_id) + await room.emit("other-join", socket.sid, nickname) + await update_field() diff --git a/back/socket.py b/back/socket.py new file mode 100644 index 0000000..02ad2ae --- /dev/null +++ b/back/socket.py @@ -0,0 +1,123 @@ +from dataclasses import dataclass, field +from typing import Any, Callable, TypeVar +from socketio import AsyncServer + + +T = TypeVar("T") + + +@dataclass +class SocketMemory: + callbacks: dict[str, dict[str, Callable]] = field(default_factory=dict) + room_data: dict[str, tuple[str, Any]] = field(default_factory=dict) + + +class Socket: + sio: AsyncServer + from_sid: str | None + sid: str | None + mem: SocketMemory + + def __init__(self, sio: AsyncServer, mem: SocketMemory | None = None) -> None: + self.sio = sio + self.from_sid = None + self.sid = None + if mem is None: + self.mem = SocketMemory() + self.on("disconnect")(lambda: None) + else: + self.mem = mem + + def __str__(self) -> str: + if self.sid is None: + return "Server Socket" + elif self.from_sid is None: + return "Client Socket {id: " + self.sid + "}" + else: + return "Room Socket {id: " + self.sid + ", room_id: " + self.from_sid + "}" + + def clear(self, sid: str) -> None: + for event in self.mem.callbacks: + if sid in self.mem.callbacks[event]: + self.mem.callbacks[event].pop(sid) + + for room in tuple(self.mem.room_data.keys()): + owner = self.mem.room_data[room][0] + if owner == sid: + self.mem.room_data.pop(room) + + def to(self, sid: str) -> "Socket": + socket = Socket(self.sio, self.mem) + socket.from_sid = self.sid + socket.sid = sid + + return socket + + def on(self, event: str): + def wrapper(f: Callable): + if event not in self.mem.callbacks: + self.mem.callbacks[event] = {} + + @self.sio.on(event) # type: ignore + async def handler(sid: str, *args): + if sid in self.mem.callbacks[event]: + await self.mem.callbacks[event][sid](*args) + + if event == "disconnect": + self.clear(sid) + + if self.sid is not None: + + async def wrapper_inner(*args): + try: + await f(*args) + except Exception as e: + print("Error:", e) + + self.mem.callbacks[event][self.sid] = wrapper_inner + + return f + + return wrapper + + async def emit(self, event: str, *args): + if self.from_sid is None: + await self.sio.emit(event, args, to=self.sid) + else: + await self.sio.emit(event, args, room=self.sid, skip_sid=self.from_sid) + + async def join(self, room: str) -> "Socket": + await self.sio.enter_room(self.sid, room) + + return self.to(room) + + async def leave(self, room: str): + await self.sio.leave_room(self.sid, room) + + def room_exists(self, room: str): + rooms = self.sio.manager.rooms.get("/", {}) + return room in rooms and len(rooms[room]) > 0 + + def room_data(self, f: Callable[[], T]) -> T: + if self.from_sid is None or self.sid is None: + raise Exception(f"{self} cannot use room data") + if self.sid in self.mem.room_data: + data = self.mem.room_data[self.sid][1] + return data + else: + data = f() + self.mem.room_data[self.sid] = self.from_sid, data + return data + + +async def connect(sio: AsyncServer, sid: str): + server = Socket(sio) + socket = server.to(sid) + + print(f"Client #{sid} connected") + + await socket.emit("msg", "Test", 123, [1, True, None]) + + @socket.on("msg") + async def test(s: str, n: int, l: list): + print(s, n * 10, l[0]) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3e37eeb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3.8' +services: + main: + image: express-reverse-proxy:latest + restart: unless-stopped + networks: + - tss-net +networks: + tss-net: + external: true \ No newline at end of file diff --git a/front/__init__.py b/front/__init__.py new file mode 100644 index 0000000..fbb0fb0 --- /dev/null +++ b/front/__init__.py @@ -0,0 +1,16 @@ +from sys import prefix +from fastapi import APIRouter +from fastapi.responses import HTMLResponse +from fastapi.staticfiles import StaticFiles + + +def get_frontend() -> APIRouter: + router = APIRouter(prefix="") + + @router.get("/", response_class=HTMLResponse) + async def index(): + with open("./static/index.html", "r") as file: + content = file.read() + return content + + return router diff --git a/jopa.py b/jopa.py new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/main.py new file mode 100644 index 0000000..93f5509 --- /dev/null +++ b/main.py @@ -0,0 +1,23 @@ +from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles +from socketio import ASGIApp + +import back +import front + +app = FastAPI() + + +router, sio = back.get_backend() +app.include_router(router) +app.include_router(front.get_frontend()) +app.mount("/static", StaticFiles(directory="static"), name="static") + + +app.mount("/", ASGIApp(sio)) + + +if __name__ == "__main__": + import uvicorn + + uvicorn.run(app, host="0.0.0.0", port=8000, reload=True) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..df75662 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +fastapi +uvicorn +python-socketio diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..fc3707d --- /dev/null +++ b/start.sh @@ -0,0 +1 @@ +python3 -m uvicorn main:app --host 0.0.0.0 --reload \ No newline at end of file diff --git a/static/colorpicker.iife.min.js b/static/colorpicker.iife.min.js new file mode 100644 index 0000000..d2c7447 --- /dev/null +++ b/static/colorpicker.iife.min.js @@ -0,0 +1 @@ +"use strict";var ColorPicker=function(){"use strict";var De=Object.defineProperty,Me=(r,t,e)=>t in r?De(r,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):r[t]=e,b=(r,t,e)=>Me(r,typeof t!="symbol"?t+"":t,e);function je(r){return r&&r.__esModule&&Object.prototype.hasOwnProperty.call(r,"default")?r.default:r}var At={exports:{}},Q=typeof Reflect=="object"?Reflect:null,Vt=Q&&typeof Q.apply=="function"?Q.apply:function(t,e,i){return Function.prototype.apply.call(t,e,i)},vt;Q&&typeof Q.ownKeys=="function"?vt=Q.ownKeys:Object.getOwnPropertySymbols?vt=function(t){return Object.getOwnPropertyNames(t).concat(Object.getOwnPropertySymbols(t))}:vt=function(t){return Object.getOwnPropertyNames(t)};function Re(r){console&&console.warn&&console.warn(r)}var Xt=Number.isNaN||function(t){return t!==t};function m(){m.init.call(this)}At.exports=m,At.exports.once=Be,m.EventEmitter=m,m.prototype._events=void 0,m.prototype._eventsCount=0,m.prototype._maxListeners=void 0;var Yt=10;function gt(r){if(typeof r!="function")throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof r)}Object.defineProperty(m,"defaultMaxListeners",{enumerable:!0,get:function(){return Yt},set:function(r){if(typeof r!="number"||r<0||Xt(r))throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received '+r+".");Yt=r}}),m.init=function(){(this._events===void 0||this._events===Object.getPrototypeOf(this)._events)&&(this._events=Object.create(null),this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},m.prototype.setMaxListeners=function(t){if(typeof t!="number"||t<0||Xt(t))throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received '+t+".");return this._maxListeners=t,this};function Ut(r){return r._maxListeners===void 0?m.defaultMaxListeners:r._maxListeners}m.prototype.getMaxListeners=function(){return Ut(this)},m.prototype.emit=function(t){for(var e=[],i=1;i0&&(s=e[0]),s instanceof Error)throw s;var a=new Error("Unhandled error."+(s?" ("+s.message+")":""));throw a.context=s,a}var l=o[t];if(l===void 0)return!1;if(typeof l=="function")Vt(l,this,e);else for(var u=l.length,c=Jt(l,u),i=0;i0&&s.length>n&&!s.warned){s.warned=!0;var a=new Error("Possible EventEmitter memory leak detected. "+s.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");a.name="MaxListenersExceededWarning",a.emitter=r,a.type=t,a.count=s.length,Re(a)}return r}m.prototype.addListener=function(t,e){return Kt(this,t,e,!1)},m.prototype.on=m.prototype.addListener,m.prototype.prependListener=function(t,e){return Kt(this,t,e,!0)};function Te(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,arguments.length===0?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function zt(r,t,e){var i={fired:!1,wrapFn:void 0,target:r,type:t,listener:e},n=Te.bind(i);return n.listener=e,i.wrapFn=n,n}m.prototype.once=function(t,e){return gt(e),this.on(t,zt(this,t,e)),this},m.prototype.prependOnceListener=function(t,e){return gt(e),this.prependListener(t,zt(this,t,e)),this},m.prototype.removeListener=function(t,e){var i,n,o,s,a;if(gt(e),n=this._events,n===void 0)return this;if(i=n[t],i===void 0)return this;if(i===e||i.listener===e)--this._eventsCount===0?this._events=Object.create(null):(delete n[t],n.removeListener&&this.emit("removeListener",t,i.listener||e));else if(typeof i!="function"){for(o=-1,s=i.length-1;s>=0;s--)if(i[s]===e||i[s].listener===e){a=i[s].listener,o=s;break}if(o<0)return this;o===0?i.shift():He(i,o),i.length===1&&(n[t]=i[0]),n.removeListener!==void 0&&this.emit("removeListener",t,a||e)}return this},m.prototype.off=m.prototype.removeListener,m.prototype.removeAllListeners=function(t){var e,i,n;if(i=this._events,i===void 0)return this;if(i.removeListener===void 0)return arguments.length===0?(this._events=Object.create(null),this._eventsCount=0):i[t]!==void 0&&(--this._eventsCount===0?this._events=Object.create(null):delete i[t]),this;if(arguments.length===0){var o=Object.keys(i),s;for(n=0;n=0;n--)this.removeListener(t,e[n]);return this};function Zt(r,t,e){var i=r._events;if(i===void 0)return[];var n=i[t];return n===void 0?[]:typeof n=="function"?e?[n.listener||n]:[n]:e?Ne(n):Jt(n,n.length)}m.prototype.listeners=function(t){return Zt(this,t,!0)},m.prototype.rawListeners=function(t){return Zt(this,t,!1)},m.listenerCount=function(r,t){return typeof r.listenerCount=="function"?r.listenerCount(t):Gt.call(r,t)},m.prototype.listenerCount=Gt;function Gt(r){var t=this._events;if(t!==void 0){var e=t[r];if(typeof e=="function")return 1;if(e!==void 0)return e.length}return 0}m.prototype.eventNames=function(){return this._eventsCount>0?vt(this._events):[]};function Jt(r,t){for(var e=new Array(t),i=0;i"u")return!1;var t=M(r).ShadowRoot;return r instanceof t||r instanceof ShadowRoot}function tr(r){var t=r.state;Object.keys(t.elements).forEach(function(e){var i=t.styles[e]||{},n=t.attributes[e]||{},o=t.elements[e];!H(o)||!I(o)||(Object.assign(o.style,i),Object.keys(n).forEach(function(s){var a=n[s];a===!1?o.removeAttribute(s):o.setAttribute(s,a===!0?"":a)}))})}function er(r){var t=r.state,e={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(t.elements.popper.style,e.popper),t.styles=e,t.elements.arrow&&Object.assign(t.elements.arrow.style,e.arrow),function(){Object.keys(t.elements).forEach(function(i){var n=t.elements[i],o=t.attributes[i]||{},s=Object.keys(t.styles.hasOwnProperty(i)?t.styles[i]:e[i]),a=s.reduce(function(l,u){return l[u]="",l},{});!H(n)||!I(n)||(Object.assign(n.style,a),Object.keys(o).forEach(function(l){n.removeAttribute(l)}))})}}const rr={name:"applyStyles",enabled:!0,phase:"write",fn:tr,effect:er,requires:["computeStyles"]};function W(r){return r.split("-")[0]}var U=Math.max,mt=Math.min,et=Math.round;function kt(){var r=navigator.userAgentData;return r!=null&&r.brands&&Array.isArray(r.brands)?r.brands.map(function(t){return t.brand+"/"+t.version}).join(" "):navigator.userAgent}function ne(){return!/^((?!chrome|android).)*safari/i.test(kt())}function rt(r,t,e){t===void 0&&(t=!1),e===void 0&&(e=!1);var i=r.getBoundingClientRect(),n=1,o=1;t&&H(r)&&(n=r.offsetWidth>0&&et(i.width)/r.offsetWidth||1,o=r.offsetHeight>0&&et(i.height)/r.offsetHeight||1);var s=Y(r)?M(r):window,a=s.visualViewport,l=!ne()&&e,u=(i.left+(l&&a?a.offsetLeft:0))/n,c=(i.top+(l&&a?a.offsetTop:0))/o,h=i.width/n,g=i.height/o;return{width:h,height:g,top:c,right:u+h,bottom:c+g,left:u,x:u,y:c}}function Dt(r){var t=rt(r),e=r.offsetWidth,i=r.offsetHeight;return Math.abs(t.width-e)<=1&&(e=t.width),Math.abs(t.height-i)<=1&&(i=t.height),{x:r.offsetLeft,y:r.offsetTop,width:e,height:i}}function oe(r,t){var e=t.getRootNode&&t.getRootNode();if(r.contains(t))return!0;if(e&&Pt(e)){var i=t;do{if(i&&r.isSameNode(i))return!0;i=i.parentNode||i.host}while(i)}return!1}function q(r){return M(r).getComputedStyle(r)}function ir(r){return["table","td","th"].indexOf(I(r))>=0}function V(r){return((Y(r)?r.ownerDocument:r.document)||window.document).documentElement}function yt(r){return I(r)==="html"?r:r.assignedSlot||r.parentNode||(Pt(r)?r.host:null)||V(r)}function se(r){return!H(r)||q(r).position==="fixed"?null:r.offsetParent}function nr(r){var t=/firefox/i.test(kt()),e=/Trident/i.test(kt());if(e&&H(r)){var i=q(r);if(i.position==="fixed")return null}var n=yt(r);for(Pt(n)&&(n=n.host);H(n)&&["html","body"].indexOf(I(n))<0;){var o=q(n);if(o.transform!=="none"||o.perspective!=="none"||o.contain==="paint"||["transform","perspective"].indexOf(o.willChange)!==-1||t&&o.willChange==="filter"||t&&o.filter&&o.filter!=="none")return n;n=n.parentNode}return null}function ct(r){for(var t=M(r),e=se(r);e&&ir(e)&&q(e).position==="static";)e=se(e);return e&&(I(e)==="html"||I(e)==="body"&&q(e).position==="static")?t:e||nr(r)||t}function Mt(r){return["top","bottom"].indexOf(r)>=0?"x":"y"}function ut(r,t,e){return U(r,mt(t,e))}function or(r,t,e){var i=ut(r,t,e);return i>e?e:i}function ae(){return{top:0,right:0,bottom:0,left:0}}function le(r){return Object.assign({},ae(),r)}function ce(r,t){return t.reduce(function(e,i){return e[i]=r,e},{})}var sr=function(t,e){return t=typeof t=="function"?t(Object.assign({},e.rects,{placement:e.placement})):t,le(typeof t!="number"?t:ce(t,st))};function ar(r){var t,e=r.state,i=r.name,n=r.options,o=e.elements.arrow,s=e.modifiersData.popperOffsets,a=W(e.placement),l=Mt(a),u=[D,T].indexOf(a)>=0,c=u?"height":"width";if(!(!o||!s)){var h=sr(n.padding,e),g=Dt(o),p=l==="y"?k:D,$=l==="y"?R:T,v=e.rects.reference[c]+e.rects.reference[l]-s[l]-e.rects.popper[c],d=s[l]-e.rects.reference[l],x=ct(o),O=x?l==="y"?x.clientHeight||0:x.clientWidth||0:0,L=v/2-d/2,f=h[p],y=O-g[c]-h[$],w=O/2-g[c]/2+L,_=ut(f,w,y),A=l;e.modifiersData[i]=(t={},t[A]=_,t.centerOffset=_-w,t)}}function lr(r){var t=r.state,e=r.options,i=e.element,n=i===void 0?"[data-popper-arrow]":i;n!=null&&(typeof n=="string"&&(n=t.elements.popper.querySelector(n),!n)||oe(t.elements.popper,n)&&(t.elements.arrow=n))}const cr={name:"arrow",enabled:!0,phase:"main",fn:ar,effect:lr,requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function it(r){return r.split("-")[1]}var ur={top:"auto",right:"auto",bottom:"auto",left:"auto"};function pr(r,t){var e=r.x,i=r.y,n=t.devicePixelRatio||1;return{x:et(e*n)/n||0,y:et(i*n)/n||0}}function ue(r){var t,e=r.popper,i=r.popperRect,n=r.placement,o=r.variation,s=r.offsets,a=r.position,l=r.gpuAcceleration,u=r.adaptive,c=r.roundOffsets,h=r.isFixed,g=s.x,p=g===void 0?0:g,$=s.y,v=$===void 0?0:$,d=typeof c=="function"?c({x:p,y:v}):{x:p,y:v};p=d.x,v=d.y;var x=s.hasOwnProperty("x"),O=s.hasOwnProperty("y"),L=D,f=k,y=window;if(u){var w=ct(e),_="clientHeight",A="clientWidth";if(w===M(e)&&(w=V(e),q(w).position!=="static"&&a==="absolute"&&(_="scrollHeight",A="scrollWidth")),w=w,n===k||(n===D||n===T)&&o===at){f=R;var E=h&&w===y&&y.visualViewport?y.visualViewport.height:w[_];v-=E-i.height,v*=l?1:-1}if(n===D||(n===k||n===R)&&o===at){L=T;var C=h&&w===y&&y.visualViewport?y.visualViewport.width:w[A];p-=C-i.width,p*=l?1:-1}}var S=Object.assign({position:a},u&&ur),N=c===!0?pr({x:p,y:v},M(e)):{x:p,y:v};if(p=N.x,v=N.y,l){var P;return Object.assign({},S,(P={},P[f]=O?"0":"",P[L]=x?"0":"",P.transform=(y.devicePixelRatio||1)<=1?"translate("+p+"px, "+v+"px)":"translate3d("+p+"px, "+v+"px, 0)",P))}return Object.assign({},S,(t={},t[f]=O?v+"px":"",t[L]=x?p+"px":"",t.transform="",t))}function fr(r){var t=r.state,e=r.options,i=e.gpuAcceleration,n=i===void 0?!0:i,o=e.adaptive,s=o===void 0?!0:o,a=e.roundOffsets,l=a===void 0?!0:a,u={placement:W(t.placement),variation:it(t.placement),popper:t.elements.popper,popperRect:t.rects.popper,gpuAcceleration:n,isFixed:t.options.strategy==="fixed"};t.modifiersData.popperOffsets!=null&&(t.styles.popper=Object.assign({},t.styles.popper,ue(Object.assign({},u,{offsets:t.modifiersData.popperOffsets,position:t.options.strategy,adaptive:s,roundOffsets:l})))),t.modifiersData.arrow!=null&&(t.styles.arrow=Object.assign({},t.styles.arrow,ue(Object.assign({},u,{offsets:t.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-placement":t.placement})}const hr={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:fr,data:{}};var wt={passive:!0};function dr(r){var t=r.state,e=r.instance,i=r.options,n=i.scroll,o=n===void 0?!0:n,s=i.resize,a=s===void 0?!0:s,l=M(t.elements.popper),u=[].concat(t.scrollParents.reference,t.scrollParents.popper);return o&&u.forEach(function(c){c.addEventListener("scroll",e.update,wt)}),a&&l.addEventListener("resize",e.update,wt),function(){o&&u.forEach(function(c){c.removeEventListener("scroll",e.update,wt)}),a&&l.removeEventListener("resize",e.update,wt)}}const vr={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:dr,data:{}};var gr={left:"right",right:"left",bottom:"top",top:"bottom"};function bt(r){return r.replace(/left|right|bottom|top/g,function(t){return gr[t]})}var mr={start:"end",end:"start"};function pe(r){return r.replace(/start|end/g,function(t){return mr[t]})}function jt(r){var t=M(r),e=t.pageXOffset,i=t.pageYOffset;return{scrollLeft:e,scrollTop:i}}function Rt(r){return rt(V(r)).left+jt(r).scrollLeft}function yr(r,t){var e=M(r),i=V(r),n=e.visualViewport,o=i.clientWidth,s=i.clientHeight,a=0,l=0;if(n){o=n.width,s=n.height;var u=ne();(u||!u&&t==="fixed")&&(a=n.offsetLeft,l=n.offsetTop)}return{width:o,height:s,x:a+Rt(r),y:l}}function wr(r){var t,e=V(r),i=jt(r),n=(t=r.ownerDocument)==null?void 0:t.body,o=U(e.scrollWidth,e.clientWidth,n?n.scrollWidth:0,n?n.clientWidth:0),s=U(e.scrollHeight,e.clientHeight,n?n.scrollHeight:0,n?n.clientHeight:0),a=-i.scrollLeft+Rt(r),l=-i.scrollTop;return q(n||e).direction==="rtl"&&(a+=U(e.clientWidth,n?n.clientWidth:0)-o),{width:o,height:s,x:a,y:l}}function Tt(r){var t=q(r),e=t.overflow,i=t.overflowX,n=t.overflowY;return/auto|scroll|overlay|hidden/.test(e+n+i)}function fe(r){return["html","body","#document"].indexOf(I(r))>=0?r.ownerDocument.body:H(r)&&Tt(r)?r:fe(yt(r))}function pt(r,t){var e;t===void 0&&(t=[]);var i=fe(r),n=i===((e=r.ownerDocument)==null?void 0:e.body),o=M(i),s=n?[o].concat(o.visualViewport||[],Tt(i)?i:[]):i,a=t.concat(s);return n?a:a.concat(pt(yt(s)))}function Ht(r){return Object.assign({},r,{left:r.x,top:r.y,right:r.x+r.width,bottom:r.y+r.height})}function br(r,t){var e=rt(r,!1,t==="fixed");return e.top=e.top+r.clientTop,e.left=e.left+r.clientLeft,e.bottom=e.top+r.clientHeight,e.right=e.left+r.clientWidth,e.width=r.clientWidth,e.height=r.clientHeight,e.x=e.left,e.y=e.top,e}function he(r,t,e){return t===ee?Ht(yr(r,e)):Y(t)?br(t,e):Ht(wr(V(r)))}function xr(r){var t=pt(yt(r)),e=["absolute","fixed"].indexOf(q(r).position)>=0,i=e&&H(r)?ct(r):r;return Y(i)?t.filter(function(n){return Y(n)&&oe(n,i)&&I(n)!=="body"}):[]}function $r(r,t,e,i){var n=t==="clippingParents"?xr(r):[].concat(t),o=[].concat(n,[e]),s=o[0],a=o.reduce(function(l,u){var c=he(r,u,i);return l.top=U(c.top,l.top),l.right=mt(c.right,l.right),l.bottom=mt(c.bottom,l.bottom),l.left=U(c.left,l.left),l},he(r,s,i));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}function de(r){var t=r.reference,e=r.element,i=r.placement,n=i?W(i):null,o=i?it(i):null,s=t.x+t.width/2-e.width/2,a=t.y+t.height/2-e.height/2,l;switch(n){case k:l={x:s,y:t.y-e.height};break;case R:l={x:s,y:t.y+t.height};break;case T:l={x:t.x+t.width,y:a};break;case D:l={x:t.x-e.width,y:a};break;default:l={x:t.x,y:t.y}}var u=n?Mt(n):null;if(u!=null){var c=u==="y"?"height":"width";switch(o){case tt:l[u]=l[u]-(t[c]/2-e[c]/2);break;case at:l[u]=l[u]+(t[c]/2-e[c]/2);break}}return l}function ft(r,t){t===void 0&&(t={});var e=t,i=e.placement,n=i===void 0?r.placement:i,o=e.strategy,s=o===void 0?r.strategy:o,a=e.boundary,l=a===void 0?Fe:a,u=e.rootBoundary,c=u===void 0?ee:u,h=e.elementContext,g=h===void 0?lt:h,p=e.altBoundary,$=p===void 0?!1:p,v=e.padding,d=v===void 0?0:v,x=le(typeof d!="number"?d:ce(d,st)),O=g===lt?qe:lt,L=r.rects.popper,f=r.elements[$?O:g],y=$r(Y(f)?f:f.contextElement||V(r.elements.popper),l,c,s),w=rt(r.elements.reference),_=de({reference:w,element:L,strategy:"absolute",placement:n}),A=Ht(Object.assign({},L,_)),E=g===lt?A:w,C={top:y.top-E.top+x.top,bottom:E.bottom-y.bottom+x.bottom,left:y.left-E.left+x.left,right:E.right-y.right+x.right},S=r.modifiersData.offset;if(g===lt&&S){var N=S[n];Object.keys(C).forEach(function(P){var K=[T,R].indexOf(P)>=0?1:-1,z=[k,R].indexOf(P)>=0?"y":"x";C[P]+=N[z]*K})}return C}function _r(r,t){t===void 0&&(t={});var e=t,i=e.placement,n=e.boundary,o=e.rootBoundary,s=e.padding,a=e.flipVariations,l=e.allowedAutoPlacements,u=l===void 0?ie:l,c=it(i),h=c?a?re:re.filter(function($){return it($)===c}):st,g=h.filter(function($){return u.indexOf($)>=0});g.length===0&&(g=h);var p=g.reduce(function($,v){return $[v]=ft(r,{placement:v,boundary:n,rootBoundary:o,padding:s})[W(v)],$},{});return Object.keys(p).sort(function($,v){return p[$]-p[v]})}function Or(r){if(W(r)===St)return[];var t=bt(r);return[pe(r),t,pe(t)]}function Lr(r){var t=r.state,e=r.options,i=r.name;if(!t.modifiersData[i]._skip){for(var n=e.mainAxis,o=n===void 0?!0:n,s=e.altAxis,a=s===void 0?!0:s,l=e.fallbackPlacements,u=e.padding,c=e.boundary,h=e.rootBoundary,g=e.altBoundary,p=e.flipVariations,$=p===void 0?!0:p,v=e.allowedAutoPlacements,d=t.options.placement,x=W(d),O=x===d,L=l||(O||!$?[bt(d)]:Or(d)),f=[d].concat(L).reduce(function(ot,X){return ot.concat(W(X)===St?_r(t,{placement:X,boundary:c,rootBoundary:h,padding:u,flipVariations:$,allowedAutoPlacements:v}):X)},[]),y=t.rects.reference,w=t.rects.popper,_=new Map,A=!0,E=f[0],C=0;C=0,z=K?"width":"height",j=ft(t,{placement:S,boundary:c,rootBoundary:h,altBoundary:g,padding:u}),B=K?P?T:D:P?R:k;y[z]>w[z]&&(B=bt(B));var _t=bt(B),Z=[];if(o&&Z.push(j[N]<=0),a&&Z.push(j[B]<=0,j[_t]<=0),Z.every(function(ot){return ot})){E=S,A=!1;break}_.set(S,Z)}if(A)for(var Ot=$?3:1,It=function(X){var dt=f.find(function(Ct){var G=_.get(Ct);if(G)return G.slice(0,X).every(function(Wt){return Wt})});if(dt)return E=dt,"break"},ht=Ot;ht>0;ht--){var Lt=It(ht);if(Lt==="break")break}t.placement!==E&&(t.modifiersData[i]._skip=!0,t.placement=E,t.reset=!0)}}const Cr={name:"flip",enabled:!0,phase:"main",fn:Lr,requiresIfExists:["offset"],data:{_skip:!1}};function ve(r,t,e){return e===void 0&&(e={x:0,y:0}),{top:r.top-t.height-e.y,right:r.right-t.width+e.x,bottom:r.bottom-t.height+e.y,left:r.left-t.width-e.x}}function ge(r){return[k,T,R,D].some(function(t){return r[t]>=0})}function Er(r){var t=r.state,e=r.name,i=t.rects.reference,n=t.rects.popper,o=t.modifiersData.preventOverflow,s=ft(t,{elementContext:"reference"}),a=ft(t,{altBoundary:!0}),l=ve(s,i),u=ve(a,n,o),c=ge(l),h=ge(u);t.modifiersData[e]={referenceClippingOffsets:l,popperEscapeOffsets:u,isReferenceHidden:c,hasPopperEscaped:h},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":c,"data-popper-escaped":h})}const Ar={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:Er};function Sr(r,t,e){var i=W(r),n=[D,k].indexOf(i)>=0?-1:1,o=typeof e=="function"?e(Object.assign({},t,{placement:r})):e,s=o[0],a=o[1];return s=s||0,a=(a||0)*n,[D,T].indexOf(i)>=0?{x:a,y:s}:{x:s,y:a}}function Pr(r){var t=r.state,e=r.options,i=r.name,n=e.offset,o=n===void 0?[0,0]:n,s=ie.reduce(function(c,h){return c[h]=Sr(h,t.rects,o),c},{}),a=s[t.placement],l=a.x,u=a.y;t.modifiersData.popperOffsets!=null&&(t.modifiersData.popperOffsets.x+=l,t.modifiersData.popperOffsets.y+=u),t.modifiersData[i]=s}const kr={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:Pr};function Dr(r){var t=r.state,e=r.name;t.modifiersData[e]=de({reference:t.rects.reference,element:t.rects.popper,strategy:"absolute",placement:t.placement})}const Mr={name:"popperOffsets",enabled:!0,phase:"read",fn:Dr,data:{}};function jr(r){return r==="x"?"y":"x"}function Rr(r){var t=r.state,e=r.options,i=r.name,n=e.mainAxis,o=n===void 0?!0:n,s=e.altAxis,a=s===void 0?!1:s,l=e.boundary,u=e.rootBoundary,c=e.altBoundary,h=e.padding,g=e.tether,p=g===void 0?!0:g,$=e.tetherOffset,v=$===void 0?0:$,d=ft(t,{boundary:l,rootBoundary:u,padding:h,altBoundary:c}),x=W(t.placement),O=it(t.placement),L=!O,f=Mt(x),y=jr(f),w=t.modifiersData.popperOffsets,_=t.rects.reference,A=t.rects.popper,E=typeof v=="function"?v(Object.assign({},t.rects,{placement:t.placement})):v,C=typeof E=="number"?{mainAxis:E,altAxis:E}:Object.assign({mainAxis:0,altAxis:0},E),S=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,N={x:0,y:0};if(w){if(o){var P,K=f==="y"?k:D,z=f==="y"?R:T,j=f==="y"?"height":"width",B=w[f],_t=B+d[K],Z=B-d[z],Ot=p?-A[j]/2:0,It=O===tt?_[j]:A[j],ht=O===tt?-A[j]:-_[j],Lt=t.elements.arrow,ot=p&&Lt?Dt(Lt):{width:0,height:0},X=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:ae(),dt=X[K],Ct=X[z],G=ut(0,_[j],ot[j]),Wt=L?_[j]/2-Ot-G-dt-C.mainAxis:It-G-dt-C.mainAxis,oi=L?-_[j]/2+Ot+G+Ct+C.mainAxis:ht+G+Ct+C.mainAxis,Ft=t.elements.arrow&&ct(t.elements.arrow),si=Ft?f==="y"?Ft.clientTop||0:Ft.clientLeft||0:0,_e=(P=S?.[f])!=null?P:0,ai=B+Wt-_e-si,li=B+oi-_e,Oe=ut(p?mt(_t,ai):_t,B,p?U(Z,li):Z);w[f]=Oe,N[f]=Oe-B}if(a){var Le,ci=f==="x"?k:D,ui=f==="x"?R:T,J=w[y],Et=y==="y"?"height":"width",Ce=J+d[ci],Ee=J-d[ui],qt=[k,D].indexOf(x)!==-1,Ae=(Le=S?.[y])!=null?Le:0,Se=qt?Ce:J-_[Et]-A[Et]-Ae+C.altAxis,Pe=qt?J+_[Et]+A[Et]-Ae-C.altAxis:Ee,ke=p&&qt?or(Se,J,Pe):ut(p?Se:Ce,J,p?Pe:Ee);w[y]=ke,N[y]=ke-J}t.modifiersData[i]=N}}const Tr={name:"preventOverflow",enabled:!0,phase:"main",fn:Rr,requiresIfExists:["offset"]};function Hr(r){return{scrollLeft:r.scrollLeft,scrollTop:r.scrollTop}}function Nr(r){return r===M(r)||!H(r)?jt(r):Hr(r)}function Br(r){var t=r.getBoundingClientRect(),e=et(t.width)/r.offsetWidth||1,i=et(t.height)/r.offsetHeight||1;return e!==1||i!==1}function Ir(r,t,e){e===void 0&&(e=!1);var i=H(t),n=H(t)&&Br(t),o=V(t),s=rt(r,n,e),a={scrollLeft:0,scrollTop:0},l={x:0,y:0};return(i||!i&&!e)&&((I(t)!=="body"||Tt(o))&&(a=Nr(t)),H(t)?(l=rt(t,!0),l.x+=t.clientLeft,l.y+=t.clientTop):o&&(l.x=Rt(o))),{x:s.left+a.scrollLeft-l.x,y:s.top+a.scrollTop-l.y,width:s.width,height:s.height}}function Wr(r){var t=new Map,e=new Set,i=[];r.forEach(function(o){t.set(o.name,o)});function n(o){e.add(o.name);var s=[].concat(o.requires||[],o.requiresIfExists||[]);s.forEach(function(a){if(!e.has(a)){var l=t.get(a);l&&n(l)}}),i.push(o)}return r.forEach(function(o){e.has(o.name)||n(o)}),i}function Fr(r){var t=Wr(r);return Qe.reduce(function(e,i){return e.concat(t.filter(function(n){return n.phase===i}))},[])}function qr(r){var t;return function(){return t||(t=new Promise(function(e){Promise.resolve().then(function(){t=void 0,e(r())})})),t}}function Vr(r){var t=r.reduce(function(e,i){var n=e[i.name];return e[i.name]=n?Object.assign({},n,i,{options:Object.assign({},n.options,i.options),data:Object.assign({},n.data,i.data)}):i,e},{});return Object.keys(t).map(function(e){return t[e]})}var me={placement:"bottom",modifiers:[],strategy:"absolute"};function ye(){for(var r=arguments.length,t=new Array(r),e=0;e{const n=Math.max(r,t,e),o=n-Math.min(r,t,e),s=o&&(n==r?(t-e)/o:n==t?2+(e-r)/o:4+(r-t)/o);return[60*(s<0?s+6:s),n&&o/n,n,i]},we=r=>r.replace(/[^0-9%.,]/g,"").split(",").map(t=>parseFloat(t)/(t.endsWith("%")?100:1)),zr=r=>{const t=document.createElement("span");t.style.display="none",t.style.color=r,document.body.append(t);const{color:e}=getComputedStyle(t);if(t.remove(),!e)return null;const[i,n,o,s]=we(e);return Kr([i/255,n/255,o/255,s])},Zr=r=>{const t=we(r).map((e,i)=>Math.min(e,i?1:255));return t.length<3||t.some(e=>isNaN(e))?null:t},be=r=>{let t;/^hsva?\(/i.test(r)?t="hsv":/^hsla?\(/i.test(r)?t="hsl":/^rgba?\(/i.test(r)?t="rgb":t="hex";const e=t==="hsv"?Zr(r):zr(r);if(!e)throw new Error("Color could not be parsed!");return e[3]=e[3]??1,{color:e,format:t}},nt=r=>r.toFixed(),Gr=(r,t)=>(""+ +r.toFixed(t)).replace(/^0\./g,"."),Jr=([r,t,e,i])=>{const n=e-e*t/2,o=Math.min(n,1-n);return[r,o?(e-n)/o:0,n,i]},Nt=([r,t,e,i])=>{const n=(o,s=(o+r/60)%6)=>e-e*t*Math.max(Math.min(s,4-s,1),0);return[n(5),n(3),n(1),i]},xt=([r,t,e,i],n,o=!1)=>{const s=o||i<1;n==="rgb"&&s&&(n="rgba");const a=n.startsWith("hs")?[nt(r),nt(t*100)+"%",nt(e*100)+"%"]:[nt(r*255),nt(t*255),nt(e*255)];return s&&a.push(Gr(i,2)),`${n}(${a.join()})`},Qr=r=>"#"+r.slice(0,r[3]<1?4:3).map(t=>Math.round(t*255).toString(16).padStart(2,"0")).join(""),ti=(r,t)=>t==="hsv"?xt(r,t):t==="hsl"?xt(Jr(r),t):t==="rgb"?xt(Nt(r),t):t==="rgba"?xt(Nt(r),t,!0):Qr(Nt(r));class F{constructor(t){if(b(this,"color"),!t)this.color=[0,0,0,1];else if(t instanceof F)this.color=[...t.color];else if(Array.isArray(t)){const[e=0,i=0,n=0,o=1]=t;this.color=[e,i,n,o]}else this.color=be(t).color}getSet(t,e){if(e===void 0)return this.color[t];const i=[...this.color];return i[t]=e,new F(i)}hue(t){return this.getSet(0,t)}saturation(t){return this.getSet(1,t)}value(t){return this.getSet(2,t)}alpha(t){return this.getSet(3,t)}string(t){return ti(this.color,t)}toString(){return this.string("hex")}clone(){return new F(this)}}class Bt extends We{constructor(t){super(),b(this,"x",0),b(this,"y",0),b(this,"$track"),b(this,"$thumb"),this.$track=t,this.$thumb=t.querySelector(".cp_thumb"),this.$track.addEventListener("pointerdown",e=>{this.$track.setPointerCapture(e.pointerId),this.handleDrag(e),e.preventDefault()}),this.$track.addEventListener("pointermove",e=>{this.$track.hasPointerCapture(e.pointerId)&&(this.handleDrag(e),e.preventDefault())}),this.$track.addEventListener("pointerup",e=>{this.$track.releasePointerCapture(e.pointerId),this.$thumb.focus(),e.preventDefault()}),this.$track.addEventListener("keydown",e=>{e.key==="ArrowLeft"?(this.handleNudge(-1,0,e.shiftKey),e.preventDefault()):e.key==="ArrowRight"?(this.handleNudge(1,0,e.shiftKey),e.preventDefault()):e.key==="ArrowUp"?(this.handleNudge(0,-1,e.shiftKey),e.preventDefault()):e.key==="ArrowDown"&&(this.handleNudge(0,1,e.shiftKey),e.preventDefault())})}handleDrag(t){const e=this.$track.getBoundingClientRect();this.fireDrag((t.clientX-e.x)/e.width,(t.clientY-e.y)/e.height)}handleNudge(t,e,i){const n=i?.1:.01;this.fireDrag(this.x+t*n,this.y+e*n)}fireDrag(t,e){t<0?t=0:t>1&&(t=1),e<0?e=0:e>1&&(e=1),this.emit("drag",t,e)}move(t,e){t!==void 0&&(this.x=t,this.$thumb.style.left=`${t*100}%`),e!==void 0&&(this.y=e,this.$thumb.style.top=`${e*100}%`)}}const ei={headless:!1,toggleStyle:"button",container:null,color:null,swatches:null,swatchesOnly:!1,enableAlpha:!0,enableEyedropper:!0,formats:["hex","rgb","hsv","hsl"],defaultFormat:"hex",submitMode:"confirm",showClearButton:!1,dismissOnOutsideClick:!0,dismissOnEscape:!0,dialogPlacement:"top",dialogOffset:8,staticPlacement:"center",staticOffset:8},xe=r=>r?r instanceof HTMLElement?r:document.querySelector(r):null,ri=(r,t,e)=>{const[i,n="center"]=t.split(" "),o=`${e}px`,s=i==="bottom"?"bottom":"top",a=i==="center";r.style[s]=a?"50%":o;const l=n==="right"?"right":"left",u=n==="center";r.style[l]=u?"50%":o,r.style.position="fixed",r.style.transform=`translate(${u?"-50%":"0%"}, ${a?"-50%":"0%"})`},ii='
',ni='
';let $t;class $e extends te.EventEmitter{constructor(t,e={}){super(),b(this,"_open",!1),b(this,"_unset",!0),b(this,"_firingChange",!1),b(this,"_format"),b(this,"_color"),b(this,"_newColor"),b(this,"_swatches"),b(this,"config"),b(this,"popper"),b(this,"isInput"),b(this,"$toggle"),b(this,"$dialog"),b(this,"$button"),b(this,"$input"),b(this,"changeHandler"),b(this,"clickHandler"),b(this,"hsvSlider"),b(this,"hueSlider"),b(this,"alphaSlider"),b(this,"$formats"),b(this,"$colorInput"),this.config={...ei,...e},t=xe(t)??document.createElement("button"),this.$toggle=t;const i=this.config.color||t.value||t.dataset.color||void 0;this.config.headless||this.createToggle(t),this._setCurrentColor(new F(i),!1),i||this.clear(!1),this.setSwatches(this.config.swatches),this.config.dismissOnOutsideClick&&window.addEventListener("pointerdown",n=>{if(!this._open)return;const o=n.target;!o.closest(".cp_dialog")&&!o.closest(".color-picker")&&this.close()}),this.config.dismissOnEscape&&window.addEventListener("keydown",n=>{if(n.key==="Escape"){const o=document.querySelector(":focus");(!o||o.closest(".cp_dialog"))&&this.close();return}}),this.close()}get isOpen(){return this._open}get color(){return this._unset?null:this._color}get swatches(){return this._swatches}get selectedColor(){return this._newColor}get format(){return this._format}get element(){return this.$toggle}createToggle(t){const e=t instanceof HTMLInputElement;this.isInput=e,this.$toggle=e?document.createElement("button"):t,this.$input=e?t:document.createElement("input"),this.isInput&&t.type=="color"&&(t.type="text"),t.replaceWith(this.$toggle),this.$input.tabIndex=-1,this.$input.readOnly=!0,this.$input.classList.add("cp_input"),this.config.toggleStyle==="input"&&this.$toggle.classList.add("cp_wide"),this.$button=document.createElement("div"),this.$button.classList.add("cp_button"),this.$button.innerHTML=ni,this.$toggle.classList.add("color-picker"),this.$toggle.setAttribute("type","button"),this.$toggle.append(this.$input,this.$button),this.changeHandler=()=>{this._firingChange||this.setColor(this.isInput?this.$input.value:this.$toggle.getAttribute("data-color"),!1)},this.clickHandler=()=>this.toggle(),this.$toggle.addEventListener("click",this.clickHandler),(e?this.$input:this.$toggle).addEventListener("change",this.changeHandler)}appendTo(t){t.append(this.element)}setSwatches(t){this._swatches=t||[],this.updateSwatches()}toggle(t=!this._open,e=!0){t?this.open(e):this.close(e)}open(t=!0){var e;if(this._open)return;this._open=!0,$t?.close(),$t=this;const i=xe(this.config.container)??document.body;i.insertAdjacentHTML("beforeend",ii),this.$dialog=i.lastElementChild,this.$colorInput=this.$dialog.querySelector(".cp_value"),this.populateDialog(),this.updateSwatches(),this.bindDialog(),this.setFormat(this.config.defaultFormat,!1),this.updateColor(),document.documentElement.contains(this.$toggle)?this.popper=Ur(this.$toggle,this.$dialog,{placement:this.config.dialogPlacement,strategy:"absolute",modifiers:[{name:"offset",options:{offset:[0,this.config.dialogOffset]}}]}):(this.popper=void 0,ri(this.$dialog,this.config.staticPlacement,this.config.staticOffset)),this.$colorInput.focus({preventScroll:!0}),(e=this.$button)==null||e.classList.add("cp_open"),setTimeout(()=>this.$dialog.classList.add("cp_open")),t&&(this.emit("open"),setTimeout(()=>this.emit("opened"),this.getAnimationDuration()))}prompt(t=!1){return new Promise(e=>{this.once("close",()=>e(this.color)),t&&this.once("closed",()=>this.destroy()),this.open()})}populateDialog(){this.config.formats&&(this.$formats=this.config.formats.map(t=>{const e=document.createElement("button");return e.className="cp_format",e.dataset.format=t,e.textContent=t.toUpperCase(),e.addEventListener("click",()=>this.setFormat(t)),e}),this.$dialog.querySelector(".cp_formats").append(...this.$formats))}bindDialog(){const t=this.$dialog.querySelector(".cp_area-hsv");this.hsvSlider=new Bt(t),this.hsvSlider.on("drag",(a,l)=>{this._setNewColor(this._newColor.saturation(a).value(1-l))});const e=this.$dialog.querySelector(".cp_slider-hue");this.hueSlider=new Bt(e),this.hueSlider.on("drag",a=>{this._setNewColor(this._newColor.hue(a*360))});const i=this.$dialog.querySelector(".cp_slider-alpha");this.config.enableAlpha?(this.alphaSlider=new Bt(i),this.alphaSlider.on("drag",a=>{this._setNewColor(this._newColor.alpha(a),!0)})):i.remove();const n=this.$dialog.querySelector(".cp_eyedrop");this.config.enableEyedropper&&"EyeDropper"in window?n.addEventListener("click",()=>{new EyeDropper().open().then(a=>{const l=new F(a.sRGBHex);this._setNewColor(l)}).catch(()=>{})}):n.remove();const o=this.$dialog.querySelector(".cp_submit");this.config.submitMode==="confirm"?o.addEventListener("click",()=>this.submit()):o.remove();const s=this.$dialog.querySelector(".cp_clear");if(this.config.showClearButton?s.addEventListener("click",()=>{this.clear(),this.close()}):s.remove(),this.$colorInput.addEventListener("input",()=>{try{const{color:a,format:l}=be(this.$colorInput.value);this.setFormat(l,!1),this._setNewColor(new F(a),!1)}catch{}}),this.$colorInput.addEventListener("keydown",a=>{a.key==="Enter"&&this.submit()}),this.$colorInput.addEventListener("dblclick",()=>{navigator.clipboard&&navigator.clipboard.writeText(this.$colorInput.value)}),this.config.swatchesOnly){const a=this.$dialog.querySelector(".cp_input-group");a&&a.remove();const l=this.$dialog.querySelector(".cp_formats");l&&l.remove(),e&&e.remove(),t&&t.remove(),i&&i.remove()}}getAnimationDuration(){const e=window.getComputedStyle(this.$toggle).getPropertyValue("--cp-delay");return parseFloat(e)*(e.endsWith("ms")?1:1e3)}close(t=!0){var e;if(!this._open)return;this._open=!1,$t=void 0,(e=this.$button)==null||e.classList.remove("cp_open");const i=this.$dialog,n=this.popper;this.$dialog=void 0,this.popper=void 0,i?.classList.remove("cp_open"),setTimeout(()=>{i?.remove(),n?.destroy(),t&&this.emit("closed")},this.getAnimationDuration()),t&&this.emit("close")}submit(t=this._newColor,e=!0){this._setCurrentColor(t,e,!0),this.close(e)}destroy(){var t;if(this.close(),(t=this.$dialog)==null||t.remove(),this.isInput){if(!this.$input)return;this.$toggle.removeChild(this.$input),this.$toggle.replaceWith(this.$input),this.$input.classList.remove("cp_input"),this.$input.removeAttribute("tabindex"),this.$input.removeAttribute("readonly"),this.changeHandler&&this.$input.removeEventListener("change",this.changeHandler)}else{if(!this.$toggle)return;this.$toggle.classList.remove("color-picker","cp_open"),this.$toggle.removeAttribute("data-color"),this.$toggle.removeAttribute("type"),this.$toggle.textContent="",this.clickHandler&&this.$toggle.removeEventListener("click",this.clickHandler)}}clear(t=!0){this._unset=!0,this.updateAppliedColor(t)}setColor(t,e=!0){if(!t)return this.clear(e);this._setCurrentColor(new F(t),e)}setFormat(t,e=!0){this._format=t,this.updateFormat(),e&&(this.updateColor(),this.updateAppliedColor(!1))}_setNewColor(t,e=!0){this._newColor=t,(this.config.submitMode==="instant"||this.config.swatchesOnly)&&(this._unset=!1,this._color=t,this.updateAppliedColor(!0)),this.updateColor(e)}_setCurrentColor(t,e=!0,i=!0){this._unset=!1,this._newColor=this._color=t,this.updateColor(i),this.updateAppliedColor(e)}updateColor(t=!0){var e,i,n,o,s,a,l,u,c,h;const g=((e=this.color)==null?void 0:e.toString())??"transparent",p=this._newColor.string("hex");(i=this.$dialog)==null||i.style.setProperty("--cp-base-color",p.substring(0,7)),(n=this.$button)==null||n.style.setProperty("--cp-current-color",g),(o=this.$dialog)==null||o.style.setProperty("--cp-current-color",g),(s=this.$dialog)==null||s.style.setProperty("--cp-color",p),(a=this.$dialog)==null||a.style.setProperty("--cp-hue",this._newColor.hue().toString()),(l=this.$dialog)==null||l.style.setProperty("--cp-alpha",this._newColor.alpha().toString()),(u=this.hsvSlider)==null||u.move(this._newColor.saturation(),1-this._newColor.value()),(c=this.hueSlider)==null||c.move(this._newColor.hue()/360),(h=this.alphaSlider)==null||h.move(this._newColor.alpha()),t&&this.$colorInput&&(this.$colorInput.value=this._newColor.string(this._format))}updateAppliedColor(t=!0){const e=this._unset?"":this._color.string(this.config.defaultFormat);this.$input&&(this.$input.value=e,this.$input.dataset.color=e),this.$toggle&&(this.$toggle.dataset.color=e),this.$button&&this.$button.classList.toggle("cp_unset",this._unset),t&&(this.emit("pick",this.color),this.$input&&(this._firingChange=!0,this.$input.dispatchEvent(new Event("change")),this._firingChange=!1))}updateFormat(){if(!this.$formats)return;this.$formats.forEach(e=>e.removeAttribute("aria-checked"));const t=this.$formats.find(e=>e.dataset.format===this._format);t&&(t.ariaChecked="true")}updateSwatches(){if(!this.$dialog)return;const t=this.$dialog.querySelector(".cp_swatches");t.textContent="",this._swatches.forEach(e=>{const i=document.createElement("button");i.className="cp_swatch",i.style.setProperty("--cp-color",e),i.dataset.color=e;const n=new F(i.dataset.color);i.addEventListener("click",()=>{this._setNewColor(n),this.config.swatchesOnly&&this.close()}),t.append(i)})}}return b($e,"Color",F),$e}(); diff --git a/static/colorpicker.min.css b/static/colorpicker.min.css new file mode 100644 index 0000000..e0f0ba4 --- /dev/null +++ b/static/colorpicker.min.css @@ -0,0 +1 @@ +[data-bs-theme=dark],[data-cp-theme=dark]{--cp-body-bg: #212529 !important;--cp-body-color: #dee2e6 !important;--cp-body-color-rgb: 222, 226, 230 !important;--cp-border-color: #666 !important;--cp-hover-color: #777 !important;--cp-button-color: #666 !important;--cp-border-color-translucent: rgba(255, 255, 255, .15) !important;--cp-tertiary-color: rgba(222, 226, 230, .5) !important}:root,[data-bs-theme=light],[data-cp-theme=light]{--cp-body-bg: #fff;--cp-body-color: #212529;--cp-body-color-rgb: 33, 37, 41;--cp-border-color: #ccc;--cp-hover-color: #999;--cp-button-color: #ccc;--cp-border-color-translucent: rgba(0, 0, 0, .175);--cp-tertiary-color: rgba(33, 37, 41, .5)}:root{box-sizing:border-box;--cp-size: 2.375rem;--cp-delay: .15s;--cp-border-radius-sm: .25rem;--cp-border-radius-lg: .5rem;--cp-swatch-width: 1.5rem;--cp-box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15);--cp-box-shadow-sm: 0 .125rem .25rem rgba(0, 0, 0, .075);--cp-bg-checker: repeating-conic-gradient(#808080 0% 25%, transparent 0% 50%) 50% / .5rem .5rem}.color-picker{display:inline-flex;cursor:pointer;padding:0;border:1px solid var(--cp-border-color);overflow:hidden;border-radius:var(--cp-border-radius-sm);background-color:transparent!important}.color-picker:not(.cp_wide){padding:1px;height:var(--cp-size);border:2px solid var(--cp-border-color);border-style:solid}.color-picker:hover{border-color:var(--cp-hover-color)!important}.cp_wide{border:1px solid var(--cp-border-color);height:var(--cp-size)}.cp_dialog input,.cp_dialog button{margin:0;border-radius:0}.cp_dialog button:hover{background-color:var(--cp-border-color-translucent)!important}.cp_input{display:none!important;pointer-events:none;margin:0;border:0}.cp_wide .cp_input{font-family:inherit;display:block!important;flex-grow:1;padding-left:.75rem;height:inherit}.cp_wide .cp_input+.cp_button{border-left:1px solid var(--cp-border-color)}.cp_wrap{display:flex;cursor:pointer}.cp_button{height:100%;aspect-ratio:1;text-align:start;overflow:hidden;cursor:pointer;display:flex;justify-content:end;align-items:end;background:linear-gradient(var(--cp-current-color),var(--cp-current-color)),var(--cp-bg-checker);border-radius:3px}.cp_button.cp_unset{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiI+PHBhdGggZmlsbD0iIzM4M2E0YyIgZD0iTTIwOC40OSAxOTEuNTFhMTIgMTIgMCAwIDEtMTcgMTdMMTI4IDE0NWwtNjMuNTEgNjMuNDlhMTIgMTIgMCAwIDEtMTctMTdMMTExIDEyOEw0Ny41MSA2NC40OWExMiAxMiAwIDAgMSAxNy0xN0wxMjggMTExbDYzLjUxLTYzLjUyYTEyIDEyIDAgMCAxIDE3IDE3TDE0NSAxMjhaIi8+PC9zdmc+),var(--cp-border-color);background-size:100% 100%}.cp_caret{background-color:var(--cp-border-color);color:var(--cp-body);font-size:calc(var(--cp-size) / 3);padding:.1rem 0 0 .1rem;border-top-left-radius:var(--cp-border-radius-sm);border-top:1px solid var(--cp-border-color-translucent);border-left:1px solid var(--cp-border-color-translucent);display:grid;place-items:center;color:var(--cp-body-color)}.cp_caret svg{transition:transform var(--cp-delay) ease-in-out}.cp_open .cp_caret svg{transform:rotate(180deg)}.cp_dialog{--cp-base-color: #000;--cp-current-color: transparent;--cp-color: transparent;--cp-hue: 0;--cp-alpha: 0;--cp-primary: hsl(var(--cp-hue), 50%, 60%);position:absolute;z-index:1055;width:17.2rem;background-color:var(--cp-body-bg);border:1px solid var(--cp-border-color-translucent);border-radius:var(--cp-border-radius-lg);box-shadow:var(--cp-box-shadow);opacity:0;pointer-events:none;transition:opacity var(--cp-delay) ease-in-out;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif}.cp_dialog-inner{display:flex;flex-direction:column;gap:.75rem;padding:.75rem}.cp_dialog.cp_open{opacity:1;pointer-events:all}.cp_sample{width:2rem;position:relative;background:linear-gradient(var(--cp-color),var(--cp-color)),var(--cp-bg-checker)}.cp_thumb{position:absolute;transform:translate(-50%,-50%);pointer-events:none;width:1.65rem;height:1.65rem;aspect-ratio:1;border:2px solid var(--cp-body-color);border-radius:9999px;box-shadow:var(--cp-box-shadow-sm);z-index:1}.cp_area{position:relative;height:8rem}.cp_slider{position:relative;height:1.25rem;border-radius:9999px}.cp_slider .cp_thumb{top:50%}.cp_area,.cp_slider,.cp_thumb{cursor:grab;overscroll-behavior:none;touch-action:none}.cp_area:active,.cp_slider:active,.cp_thumb:active{cursor:grabbing}.cp_area-hsv{border-radius:var(--cp-border-radius-lg) var(--cp-border-radius-lg) 0px 0px;background:linear-gradient(to top,rgba(0,0,0,var(--cp-alpha)),transparent),linear-gradient(to left,hsla(var(--cp-hue),100%,50%,var(--cp-alpha)),rgba(255,255,255,var(--cp-alpha))),var(--cp-bg-checker)}.cp_area-hsv .cp_thumb{background-color:var(--cp-color)}.cp_slider-hue{background:linear-gradient(to right,red,#ff0,#0f0,#0ff,#00f,#f0f,red)}.cp_slider-hue .cp_thumb{background-color:hsl(var(--cp-hue),100%,50%)}.cp_slider-alpha{background:linear-gradient(to right,transparent,var(--cp-base-color)),var(--cp-bg-checker)}.cp_slider-alpha .cp_thumb{background-color:var(--cp-color)}.color-picker:focus-visible,.cp_dialog *:focus-visible{transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;border-color:var(--cp-body-bg);box-shadow:0 0 0 2px var(--cp-tertiary-color);outline:0}.cp_swatches{display:flex;flex-wrap:wrap;gap:.25rem;justify-content:center}.cp_swatches:empty{display:none}.cp_swatch{--cp-color: transparent;width:var(--cp-swatch-width);aspect-ratio:1;padding:0;border:1px solid rgba(0,0,0,.2)!important;border-radius:var(--cp-border-radius-sm)!important;overflow:hidden;cursor:pointer;background:linear-gradient(var(--cp-color),var(--cp-color)),var(--cp-bg-checker);transition:all .2s ease-in-out}.cp_swatch:hover{transform:scale(1.15)}.cp_formats{display:flex;gap:.25rem}.cp_formats:empty{display:none}.cp_format{flex:1 1 auto;border:none;border-radius:9999px!important;padding:.125rem .25rem;background:none;color:var(--cp-body-color);font-size:1rem;cursor:pointer;transition:color .1s ease-out,background-color .1s ease-out}.cp_format:hover{background-color:var(--cp-border-color-translucent)!important}.cp_format[aria-checked=true]{color:var(--cp-body-color);background-color:var(--cp-button-color)}.cp_input-group{display:flex;gap:1px;box-shadow:var(--cp-box-shadow-sm)}.cp_input-group>:first-child{border-radius:var(--cp-border-radius-sm) 0 0 var(--cp-border-radius-sm)}.cp_input-group>:last-child{border-radius:0 var(--cp-border-radius-sm) var(--cp-border-radius-sm) 0}.cp_input-group>:focus-visible{z-index:1}.cp_value{flex:1 1 auto;min-width:0;width:0;text-align:center;border:none;border-radius:0;padding:.25rem .375rem;font-size:.9rem;font-family:inherit;color:var(--cp-body-color);background-color:var(--cp-border-color-translucent)}.cp_action{border:none;padding:.35rem .5rem;color:var(--cp-body-color);background-color:var(--cp-button-color);cursor:pointer;transition:background-color .1s ease-out;display:flex;place-items:center}.cp_action:hover{background-color:var(--cp-border-color-translucent)}.cp_icon{font-size:1.15rem} diff --git a/static/game.js b/static/game.js new file mode 100644 index 0000000..09985e5 --- /dev/null +++ b/static/game.js @@ -0,0 +1,226 @@ +// CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) { +// if (w < 2 * r) r = w / 2; +// if (h < 2 * r) r = h / 2; +// this.beginPath(); +// this.moveTo(x+r, y); +// this.arcTo(x+w, y, x+w, y+h, r); +// this.arcTo(x+w, y+h, x, y+h, r); +// this.arcTo(x, y+h, x, y, r); +// this.arcTo(x, y, x+w, y, r); +// this.closePath(); +// return this; +// } + +function startGame() { + const canvas = document.querySelector("canvas"); + if (!canvas) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + ctx.font = "24px Montserrat" + ctx.imageSmoothingEnabled = true + + + canvas.addEventListener('mousemove', (e) => { + + const rect = canvas.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + if (x < 0 || y < 0 || x > rect.width || y > rect.height) return; + + // Adjust for potential canvas scaling (e.g. high DPI) + const scaleX = canvas.width / rect.width; + const scaleY = canvas.height / rect.height; + socket.emit("cursor", x * scaleX, y * scaleY); + }) + + canvas.addEventListener('click', e => click(true, e, canvas)) + canvas.addEventListener('contextmenu', e => click(false, e, canvas)) + + gameLoop(ctx) +} + +/** + * + * @param {boolean} left + * @param {PointerEvent} e + * @param {HTMLCanvasElement} canvas + */ +function click(left, e, canvas) { + e.preventDefault() + + const rect = canvas.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + if (x < 0 || y < 0 || x > rect.width || y > rect.height) return; + + // Adjust for potential canvas scaling (e.g. high DPI) + const scaleX = canvas.width / rect.width; + const scaleY = canvas.height / rect.height; + socket.emit("click", left, Math.floor((x * scaleX - offsetX()) / CELL_SIZE), Math.floor((y * scaleY - offsetY()) / CELL_SIZE)); +} + +/** + * + * @param {CanvasRenderingContext2D} ctx + */ +function gameLoop(ctx) { + ctx.save() + ctx.clearRect(0, 0, 800, 600) + + + for (let x = 0; x < data.field.length; x++) + for (let y = 0; y < data.field[x].length; y++) + drawCell(ctx, x, y) + + const x = 400 + const y = 600 - (600 - (CELL_SIZE * MAX_HEIGTH)) * 0.5 + + ctx.textAlign = 'center' + ctx.textBaseline = 'middle' + ctx.fillStyle = getColor('fg') + + const w = data.field.length + const h = w && data.field[0].length + ctx.fillText(`${data.text} 🚩 ${data.flags} 💣 ${data.bombs} Поле ${w}x${h}`.trim(), x, y) + + for (const sid of Object.keys(data.cursors)) + drawCursor(ctx, sid) + + + ctx.restore(); + window.requestAnimationFrame(() => gameLoop(ctx)) +} + +const HALF_CELL_SIZE = 16 +const CELL_SIZE = HALF_CELL_SIZE * 2 +const MAX_WIDTH = 25 +const MAX_HEIGTH = 15 + +function offsetX() { + return (MAX_WIDTH - data.field.length) * HALF_CELL_SIZE +} +function offsetY() { + return (MAX_HEIGTH - (data.field.length && data.field[0].length)) * HALF_CELL_SIZE +} + +/** + * + * @param {CanvasRenderingContext2D} ctx + * @param {number} cx + * @param {number} cy + */ +function drawCell(ctx, cx, cy) { + let cell = data.field[cx][cy] + switch (cell) { + case "": + case "🚩": + case "❓": + ctx.fillStyle = getColor("bgCellClosed") + break; + case " ": + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + case "8": + ctx.fillStyle = getColor("bgCellOpen") + break + default: + ctx.fillStyle = getColor("bgCellBomb") + break + } + ctx.strokeStyle = getColor("bg") + ctx.lineWidth = 4 + + const x = cx * CELL_SIZE + offsetX(); + const y = cy * CELL_SIZE + offsetY(); + ctx.fillRect(x, y, CELL_SIZE, CELL_SIZE) + ctx.strokeRect(x, y, CELL_SIZE, CELL_SIZE) + switch (cell) { + case "": + case " ": + return + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + case "8": + ctx.fillStyle = getColor('fgCell' + cell) + break + default: + ctx.fillStyle = "white" + break + } + + ctx.textAlign = "center" + ctx.textBaseline = "middle" + ctx.fillText(cell, x + HALF_CELL_SIZE, y + HALF_CELL_SIZE) +} + +/** + * + * @param {CanvasRenderingContext2D} ctx + * @param {string} sid + * @param {number} x + * @param {number} y + * @param {number} w + * @param {number} h + */ +function drawCursor(ctx, sid, w = 15, h = 20) { + const { x, y, nickname } = data.cursors[sid]; + + const a = sid.charCodeAt(0) % 256; + const b = sid.charCodeAt(1) % 256; + const c = sid.charCodeAt(2) % 3; + + const aStr = a < 16 ? "0" + a.toString(16) : a.toString(16); + const bStr = b < 16 ? "0" + b.toString(16) : b.toString(16); + + let color = "#"; + switch (c) { + case 0: + color += "ff" + aStr + bStr; + break; + case 1: + color += aStr + "ff" + bStr; + break; + default: + color += aStr + bStr + "ff"; + break; + } + + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x + w, y + h * 0.6); + ctx.lineTo(x + w * 0.6, y + h * 0.5); + ctx.lineTo(x + w, y + h * 0.9); + ctx.lineTo(x + w * 0.9, y + h); + ctx.lineTo(x + w * 0.5, y + h * 0.6); + ctx.lineTo(x + w * 0.6, y + h); + ctx.closePath(); + + ctx.fillStyle = color; + ctx.fill(); + ctx.strokeStyle = "black" + ctx.lineWidth = 1 + ctx.stroke(); + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + ctx.textBaseline = 'top'; + ctx.fillStyle = "#00000077"; + const { width: tw, ideographicBaseline: th } = ctx.measureText(nickname) + ctx.beginPath(); + ctx.roundRect(x + w * 2, y, tw + w * 2, Math.abs(th), [w]) + ctx.fill() + ctx.fillStyle = "#ffffff" + ctx.fillText(nickname, x + w * 3, y) +} \ No newline at end of file diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..c997b50 --- /dev/null +++ b/static/index.html @@ -0,0 +1,166 @@ + + + + + + + Сапер мультиплеер + + + + + + + + +
+
+
+

Ваш никнейм

+
+
+ +
+
+
+
+
+

Присоединиться

+
+
+ +
+
+ +
+
+
+

Новая комната

+
+ + X + +
+ Бомб: + +
+
+ +
+
+
+
+
+
+

Основные цвета

+
+
+ + + +
+
+
+

Цвета чата

+
+
+
+
+

Уведомления

+
+
+ + +
+
+
+ +
+

Исходящие

+
+
+ + +
+
+
+
+

Входящие

+
+
+ + +
+
+
+
+
+

Цвета клеток

+
+
+
+
+

Фон открытой

+
+
+ +
+
+
+ +
+

Фон закрытой

+
+
+ +
+
+
+
+

Фон с бомбой

+
+
+ +
+
+
+
+

Цвета цифер

+
+
+ + + + + + + + +
+
+ + + + + + + +
+ + + \ No newline at end of file diff --git a/static/main.js b/static/main.js new file mode 100644 index 0000000..4f1ed3b --- /dev/null +++ b/static/main.js @@ -0,0 +1,208 @@ +const socket = io("/"); + +const data = { is_host: false, cursors: {}, field: [], flags: 0, bombs: 0, text: "" } + +/** @returns {string} */ +function getNickname() { + return document.getElementById("nickname").value +} +/** @returns {string} */ +function getRoomId() { + return document.getElementById("room-id").value +} +/** @returns {string} */ +function getWidth() { + return document.getElementById("width").value +} +/** @returns {string} */ +function getHeight() { + return document.getElementById("height").value +} +/** @returns {string} */ +function getBombs() { + return document.getElementById("bombs").value +} + +function create() { + data.is_host = true; + socket.emit("create-room", getNickname(), getWidth(), getHeight(), getBombs()) +} + +function join() { + data.is_host = false; + socket.emit("join-room", getNickname(), getRoomId()) +} + +function restart() { + socket.emit("restart") +} + +/** + * @argument {string} block + */ +function show(block) { + document.querySelectorAll('[data-block]').forEach(x => x.style.display = 'none'); + document.querySelectorAll('[data-block="' + block + '"]').forEach(x => x.style.display = ''); +} + +/** + * @argument {string} msg + */ +function roomError(msg) { + alert(msg) +} +/** + * @argument {string} roomId + */ +function roomJoin(roomId) { + // alert(`Вы подключились к комнате #${roomId}`) + localStorage.setItem("__saper_nickname", getNickname()) + otherJoin(socket.id, getNickname() + (data.is_host ? " (Вы 👑)" : " (Вы)"), true) + chatMessage('service', "Вы подключены к комнате #" + roomId) + show('game') + + startGame() +} + +/** + * @param {"service" | "self" | "other"} type + * @param {string} message + */ +function chatMessage(type, message) { + const msg = document.createElement('div') + msg.classList.add('chat-' + type + '-msg') + msg.innerText = message + document.getElementById('chat').appendChild(msg) + msg.scrollIntoView({ 'behavior': 'smooth' }) +} +/** + * + * @param {string} sid + * @param {string} nickname + * @param {boolean} onlyList + */ +function otherJoin(sid, nickname, onlyList = false) { + if (!document.querySelector('[data-sid="' + sid + '"]')) { + const user = document.createElement('div'); + user.classList.add('chat-service-msg') + user.innerText = nickname; + user.setAttribute('data-sid', sid) + document.getElementById('users').appendChild(user) + } + + if (!onlyList) { + chatMessage('service', "К комнате подключился " + nickname) + socket.emit("about-me") + } +} + +/** + * + * @param {string} sid + */ +function otherDisconnect(sid, nickname) { + const user = document.querySelector('[data-sid="' + sid + '"]') + if (user) + user.remove(); + + chatMessage('service', "От комнаты отключился " + nickname) + if (data.cursors[sid]) + delete data.cursors[sid]; +} + +function chat(nickname, msg) { + chatMessage("other", nickname + ": " + msg) +} + +function cursor(sid, nickname, x, y) { + data.cursors[sid] = { x, y, nickname } +} + +function updateField(field, flags, bombs, text) { + data.field = field + data.flags = flags + data.bombs = bombs + data.text = text +} + +socket.on("room-error", roomError) +socket.on("room-join", roomJoin) +socket.on("other-join", otherJoin) +socket.on("about-me", (sid, nickname) => otherJoin(sid, nickname, true)) +socket.on("other-disconnect", otherDisconnect) +socket.on("chat", chat) +socket.on("reload-page", () => window.location.reload()) +socket.on("cursor", cursor) +socket.on('update-field', updateField) + +document.getElementById("nickname").value = localStorage.getItem("__saper_nickname") || "" + +const chatMsg = document.getElementById("chat-msg") + +const colors = { + bg: new ColorPicker('#color-bg'), + bg2: new ColorPicker('#color-bg-2'), + fg: new ColorPicker('#color-fg'), + bgChatService: new ColorPicker('#color-bg-chat-service'), + fgChatService: new ColorPicker('#color-fg-chat-service'), + bgChatSelf: new ColorPicker('#color-bg-chat-self'), + fgChatSelf: new ColorPicker('#color-fg-chat-self'), + bgChatOther: new ColorPicker('#color-bg-chat-other'), + fgChatOther: new ColorPicker('#color-fg-chat-other'), + bgCellOpen: new ColorPicker("#color-bg-cell-open"), + bgCellClosed: new ColorPicker("#color-bg-cell-closed"), + bgCellBomb: new ColorPicker("#color-bg-cell-bomb"), + fgCell1: new ColorPicker("#color-fg-cell-1"), + fgCell2: new ColorPicker("#color-fg-cell-2"), + fgCell3: new ColorPicker("#color-fg-cell-3"), + fgCell4: new ColorPicker("#color-fg-cell-4"), + fgCell5: new ColorPicker("#color-fg-cell-5"), + fgCell6: new ColorPicker("#color-fg-cell-6"), + fgCell7: new ColorPicker("#color-fg-cell-7"), + fgCell8: new ColorPicker("#color-fg-cell-8"), +} + +/** + * + * @param {keyof colors} color + * @returns {string} + */ +function getColor(color) { + return colors[color].color.string('hex') +} + +const root = document.querySelector('html') +const computeStyles = window.getComputedStyle(root); +for (const color_e of Object.values(colors)) { + const color_var = color_e.element.id.replace('color-', '--') + const color_key = "__saper_" + color_e.element.id + const color = localStorage.getItem(color_key) || computeStyles.getPropertyValue(color_var); + color_e.setColor(color) + const text = document.createElement('span') + text.innerText = color_var.replace('--', '') + color_e.element.appendChild(text) + root.style.setProperty(color_var, color) + color_e.on('pick', c => { + const color = c.string('hex') + console.log(color) + root.style.setProperty(color_var, color) + localStorage.setItem(color_key, color) + + }) +} + +chatMsg.addEventListener("keyup", function (event) { + if (event.key === "Enter") { + const msg = chatMsg.value; + chatMsg.value = ""; + socket.emit("chat", msg) + chatMessage("self", "Вы: " + msg) + } +}); + + +document.getElementById("room-id").addEventListener("keyup", function (event) { + if (event.key === "Enter") { + join() + } +}); \ No newline at end of file diff --git a/static/socket.io.js b/static/socket.io.js new file mode 100644 index 0000000..9562164 --- /dev/null +++ b/static/socket.io.js @@ -0,0 +1,5523 @@ +/*! + * Socket.IO v4.8.0 + * (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 _arrayLikeToArray(r, a) { + (null == a || a > r.length) && (a = r.length); + for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; + return n; + } + function _arrayWithoutHoles(r) { + if (Array.isArray(r)) return _arrayLikeToArray(r); + } + function _construct(t, e, r) { + if (_isNativeReflectConstruct()) + return Reflect.construct.apply(null, arguments); + var o = [null]; + o.push.apply(o, e); + var p = new (t.bind.apply(t, o))(); + return (r && _setPrototypeOf(p, r.prototype), p); + } + function _defineProperties(e, r) { + for (var t = 0; t < r.length; t++) { + var o = r[t]; + ((o.enumerable = o.enumerable || !1), + (o.configurable = !0), + "value" in o && (o.writable = !0), + Object.defineProperty(e, _toPropertyKey(o.key), o)); + } + } + function _createClass(e, r, t) { + return ( + r && _defineProperties(e.prototype, r), + t && _defineProperties(e, t), + Object.defineProperty(e, "prototype", { + writable: !1, + }), + e + ); + } + function _createForOfIteratorHelper(r, e) { + var t = + ("undefined" != typeof Symbol && r[Symbol.iterator]) || r["@@iterator"]; + if (!t) { + if ( + Array.isArray(r) || + (t = _unsupportedIterableToArray(r)) || + (e && r && "number" == typeof r.length) + ) { + t && (r = t); + var n = 0, + F = function () {}; + return { + s: F, + n: function () { + return n >= r.length + ? { + done: !0, + } + : { + done: !1, + value: r[n++], + }; + }, + e: function (r) { + throw r; + }, + 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 o, + a = !0, + u = !1; + return { + s: function () { + t = t.call(r); + }, + n: function () { + var r = t.next(); + return ((a = r.done), r); + }, + e: function (r) { + ((u = !0), (o = r)); + }, + f: function () { + try { + a || null == t.return || t.return(); + } finally { + if (u) throw o; + } + }, + }; + } + function _extends() { + return ( + (_extends = Object.assign + ? Object.assign.bind() + : function (n) { + for (var e = 1; e < arguments.length; e++) { + var t = arguments[e]; + for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); + } + return n; + }), + _extends.apply(null, arguments) + ); + } + function _getPrototypeOf(t) { + return ( + (_getPrototypeOf = Object.setPrototypeOf + ? Object.getPrototypeOf.bind() + : function (t) { + return t.__proto__ || Object.getPrototypeOf(t); + }), + _getPrototypeOf(t) + ); + } + function _inheritsLoose(t, o) { + ((t.prototype = Object.create(o.prototype)), + (t.prototype.constructor = t), + _setPrototypeOf(t, o)); + } + function _isNativeFunction(t) { + try { + return -1 !== Function.toString.call(t).indexOf("[native code]"); + } catch (n) { + return "function" == typeof t; + } + } + function _isNativeReflectConstruct() { + try { + var t = !Boolean.prototype.valueOf.call( + Reflect.construct(Boolean, [], function () {}), + ); + } catch (t) {} + return (_isNativeReflectConstruct = function () { + return !!t; + })(); + } + function _iterableToArray(r) { + if ( + ("undefined" != typeof Symbol && null != r[Symbol.iterator]) || + null != r["@@iterator"] + ) + return Array.from(r); + } + function _nonIterableSpread() { + throw new TypeError( + "Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.", + ); + } + function _setPrototypeOf(t, e) { + return ( + (_setPrototypeOf = Object.setPrototypeOf + ? Object.setPrototypeOf.bind() + : function (t, e) { + return ((t.__proto__ = e), t); + }), + _setPrototypeOf(t, e) + ); + } + function _toConsumableArray(r) { + return ( + _arrayWithoutHoles(r) || + _iterableToArray(r) || + _unsupportedIterableToArray(r) || + _nonIterableSpread() + ); + } + function _toPrimitive(t, r) { + if ("object" != typeof t || !t) return t; + var e = t[Symbol.toPrimitive]; + if (void 0 !== e) { + var i = e.call(t, r || "default"); + if ("object" != typeof i) return i; + throw new TypeError("@@toPrimitive must return a primitive value."); + } + return ("string" === r ? String : Number)(t); + } + function _toPropertyKey(t) { + var i = _toPrimitive(t, "string"); + return "symbol" == typeof i ? i : i + ""; + } + function _typeof(o) { + "@babel/helpers - typeof"; + + return ( + (_typeof = + "function" == typeof Symbol && "symbol" == typeof Symbol.iterator + ? function (o) { + return typeof o; + } + : function (o) { + return o && + "function" == typeof Symbol && + o.constructor === Symbol && + o !== Symbol.prototype + ? "symbol" + : typeof o; + }), + _typeof(o) + ); + } + function _unsupportedIterableToArray(r, a) { + if (r) { + if ("string" == typeof r) return _arrayLikeToArray(r, a); + var t = {}.toString.call(r).slice(8, -1); + return ( + "Object" === t && r.constructor && (t = r.constructor.name), + "Map" === t || "Set" === t + ? Array.from(r) + : "Arguments" === t || + /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) + ? _arrayLikeToArray(r, a) + : void 0 + ); + } + } + function _wrapNativeSuper(t) { + var r = "function" == typeof Map ? new Map() : void 0; + return ( + (_wrapNativeSuper = function (t) { + if (null === t || !_isNativeFunction(t)) return t; + if ("function" != typeof t) + throw new TypeError( + "Super expression must either be null or a function", + ); + if (void 0 !== r) { + if (r.has(t)) return r.get(t); + r.set(t, Wrapper); + } + function Wrapper() { + return _construct(t, arguments, _getPrototypeOf(this).constructor); + } + return ( + (Wrapper.prototype = Object.create(t.prototype, { + constructor: { + value: Wrapper, + enumerable: !1, + writable: !0, + configurable: !0, + }, + })), + _setPrototypeOf(Wrapper, t) + ); + }), + _wrapNativeSuper(t) + ); + } + + 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 = 0; i < chars.length; i++) { + lookup$1[chars.charCodeAt(i)] = i; + } + 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; /* State.READ_HEADER */ + var expectedLength = -1; + var isBinary = false; + return new TransformStream({ + transform: function transform(chunk, controller) { + chunks.push(chunk); + while (true) { + if (state === 0 /* State.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 /* State.READ_PAYLOAD */; + } else if (expectedLength === 126) { + state = 1 /* State.READ_EXTENDED_LENGTH_16 */; + } else { + state = 2 /* State.READ_EXTENDED_LENGTH_64 */; + } + } else if (state === 1 /* State.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 /* State.READ_PAYLOAD */; + } else if (state === 2 /* State.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 /* State.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 /* State.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 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 globalThisShim = (function () { + if (typeof self !== "undefined") { + return self; + } else if (typeof window !== "undefined") { + return window; + } else { + return Function("return this")(); + } + })(); + var defaultBinaryType = "arraybuffer"; + function createCookieJar() {} + + 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; + } + /** + * Generates a random 8-characters string. + */ + function randomString() { + return ( + Date.now().toString(36).substring(3) + + Math.random().toString(36).substring(2, 5) + ); + } + + // imported from https://github.com/galkn/querystring + /** + * Compiles a querystring + * Returns string representation of the object + * + * @param {Object} + * @api private + */ + function encode(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) { + function TransportError(reason, description, context) { + var _this; + _this = _Error.call(this, reason) || this; + _this.description = description; + _this.context = context; + _this.type = "TransportError"; + return _this; + } + _inheritsLoose(TransportError, _Error); + return TransportError; + })(/*#__PURE__*/ _wrapNativeSuper(Error)); + var Transport = /*#__PURE__*/ (function (_Emitter) { + /** + * Transport abstract constructor. + * + * @param {Object} opts - options + * @protected + */ + function Transport(opts) { + var _this2; + _this2 = _Emitter.call(this) || this; + _this2.writable = false; + installTimerFunctions(_this2, opts); + _this2.opts = opts; + _this2.query = opts.query; + _this2.socket = opts.socket; + _this2.supportsBinary = !opts.forceBase64; + return _this2; + } + /** + * Emits an error. + * + * @param {String} reason + * @param description + * @param context - the error context + * @return {Transport} for chaining + * @protected + */ + _inheritsLoose(Transport, _Emitter); + var _proto = Transport.prototype; + _proto.onError = function onError(reason, description, context) { + _Emitter.prototype.emitReserved.call( + this, + "error", + new TransportError(reason, description, context), + ); + return this; + }; + /** + * Opens the transport. + */ + _proto.open = function open() { + this.readyState = "opening"; + this.doOpen(); + return this; + }; + /** + * Closes the transport. + */ + _proto.close = function close() { + if (this.readyState === "opening" || this.readyState === "open") { + this.doClose(); + this.onClose(); + } + return this; + }; + /** + * Sends multiple packets. + * + * @param {Array} packets + */ + _proto.send = function send(packets) { + if (this.readyState === "open") { + this.write(packets); + } + }; + /** + * Called upon open + * + * @protected + */ + _proto.onOpen = function onOpen() { + this.readyState = "open"; + this.writable = true; + _Emitter.prototype.emitReserved.call(this, "open"); + }; + /** + * Called with data. + * + * @param {String} data + * @protected + */ + _proto.onData = function onData(data) { + var packet = decodePacket(data, this.socket.binaryType); + this.onPacket(packet); + }; + /** + * Called with a decoded packet. + * + * @protected + */ + _proto.onPacket = function onPacket(packet) { + _Emitter.prototype.emitReserved.call(this, "packet", packet); + }; + /** + * Called upon close. + * + * @protected + */ + _proto.onClose = function onClose(details) { + this.readyState = "closed"; + _Emitter.prototype.emitReserved.call(this, "close", details); + }; + /** + * Pauses the transport, in order not to lose packets during an upgrade. + * + * @param onPause + */ + _proto.pause = function pause(onPause) {}; + _proto.createUri = 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) + ); + }; + _proto._hostname = function _hostname() { + var hostname = this.opts.hostname; + return hostname.indexOf(":") === -1 ? hostname : "[" + hostname + "]"; + }; + _proto._port = 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 ""; + } + }; + _proto._query = function _query(query) { + var encodedQuery = encode(query); + return encodedQuery.length ? "?" + encodedQuery : ""; + }; + return Transport; + })(Emitter); + + var Polling = /*#__PURE__*/ (function (_Transport) { + function Polling() { + var _this; + _this = _Transport.apply(this, arguments) || this; + _this._polling = false; + return _this; + } + _inheritsLoose(Polling, _Transport); + var _proto = Polling.prototype; + /** + * Opens the socket (triggers polling). We write a PING message to determine + * when the transport is open. + * + * @protected + */ + _proto.doOpen = function doOpen() { + this._poll(); + }; + /** + * Pauses polling. + * + * @param {Function} onPause - callback upon buffers are flushed and transport is paused + * @package + */ + _proto.pause = 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 + */ + _proto._poll = function _poll() { + this._polling = true; + this.doPoll(); + this.emitReserved("poll"); + }; + /** + * Overloads onData to detect payloads. + * + * @protected + */ + _proto.onData = 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 + */ + _proto.doClose = 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 + */ + _proto.write = 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 + */ + _proto.uri = 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] = randomString(); + } + if (!this.supportsBinary && !query.sid) { + query.b64 = 1; + } + return this.createUri(schema, query); + }; + return _createClass(Polling, [ + { + key: "name", + get: function get() { + return "polling"; + }, + }, + ]); + })(Transport); + + // 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; + + function empty() {} + var BaseXHR = /*#__PURE__*/ (function (_Polling) { + /** + * XHR Polling constructor. + * + * @param {Object} opts + * @package + */ + function BaseXHR(opts) { + var _this; + _this = _Polling.call(this, opts) || this; + 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; + } + return _this; + } + /** + * Sends data. + * + * @param {String} data to send. + * @param {Function} called upon flush. + * @private + */ + _inheritsLoose(BaseXHR, _Polling); + var _proto = BaseXHR.prototype; + _proto.doWrite = function doWrite(data, fn) { + var _this2 = this; + var req = this.request({ + method: "POST", + data: data, + }); + req.on("success", fn); + req.on("error", function (xhrStatus, context) { + _this2.onError("xhr post error", xhrStatus, context); + }); + }; + /** + * Starts a poll cycle. + * + * @private + */ + _proto.doPoll = function doPoll() { + var _this3 = this; + var req = this.request(); + req.on("data", this.onData.bind(this)); + req.on("error", function (xhrStatus, context) { + _this3.onError("xhr poll error", xhrStatus, context); + }); + this.pollXhr = req; + }; + return BaseXHR; + })(Polling); + var Request = /*#__PURE__*/ (function (_Emitter) { + /** + * Request constructor + * + * @param {Object} options + * @package + */ + function Request(createRequest, uri, opts) { + var _this4; + _this4 = _Emitter.call(this) || this; + _this4.createRequest = createRequest; + installTimerFunctions(_this4, opts); + _this4._opts = opts; + _this4._method = opts.method || "GET"; + _this4._uri = uri; + _this4._data = undefined !== opts.data ? opts.data : null; + _this4._create(); + return _this4; + } + /** + * Creates the XHR object and sends the request. + * + * @private + */ + _inheritsLoose(Request, _Emitter); + var _proto2 = Request.prototype; + _proto2._create = function _create() { + var _this5 = 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 = this.createRequest(opts)); + try { + xhr.open(this._method, this._uri, true); + try { + if (this._opts.extraHeaders) { + // @ts-ignore + 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 = _this5._opts.cookieJar) === null || _a === void 0 + ? void 0 + : _a.parseCookies( + // @ts-ignore + xhr.getResponseHeader("set-cookie"), + ); + } + if (4 !== xhr.readyState) return; + if (200 === xhr.status || 1223 === xhr.status) { + _this5._onLoad(); + } else { + // make sure the `error` event handler that's user-set + // does not throw in the same tick and gets caught here + _this5.setTimeoutFn(function () { + _this5._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 () { + _this5._onError(e); + }, 0); + return; + } + if (typeof document !== "undefined") { + this._index = Request.requestsCount++; + Request.requests[this._index] = this; + } + }; + /** + * Called upon error. + * + * @private + */ + _proto2._onError = function _onError(err) { + this.emitReserved("error", err, this._xhr); + this._cleanup(true); + }; + /** + * Cleans up house. + * + * @private + */ + _proto2._cleanup = 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 + */ + _proto2._onLoad = function _onLoad() { + var data = this._xhr.responseText; + if (data !== null) { + this.emitReserved("data", data); + this.emitReserved("success"); + this._cleanup(); + } + }; + /** + * Aborts the request. + * + * @package + */ + _proto2.abort = 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 hasXHR2 = (function () { + var xhr = newRequest({ + xdomain: false, + }); + return xhr && xhr.responseType !== null; + })(); + /** + * HTTP long-polling based on the built-in `XMLHttpRequest` object. + * + * Usage: browser + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest + */ + var XHR = /*#__PURE__*/ (function (_BaseXHR) { + function XHR(opts) { + var _this6; + _this6 = _BaseXHR.call(this, opts) || this; + var forceBase64 = opts && opts.forceBase64; + _this6.supportsBinary = hasXHR2 && !forceBase64; + return _this6; + } + _inheritsLoose(XHR, _BaseXHR); + var _proto3 = XHR.prototype; + _proto3.request = function request() { + var opts = + arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + _extends( + opts, + { + xd: this.xd, + }, + this.opts, + ); + return new Request(newRequest, this.uri(), opts); + }; + return XHR; + })(BaseXHR); + function newRequest(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) {} + } + } + + // detect ReactNative environment + var isReactNative = + typeof navigator !== "undefined" && + typeof navigator.product === "string" && + navigator.product.toLowerCase() === "reactnative"; + var BaseWS = /*#__PURE__*/ (function (_Transport) { + function BaseWS() { + return _Transport.apply(this, arguments) || this; + } + _inheritsLoose(BaseWS, _Transport); + var _proto = BaseWS.prototype; + _proto.doOpen = function doOpen() { + 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 = this.createSocket(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 + */ + _proto.addEventListeners = function addEventListeners() { + var _this = this; + this.ws.onopen = function () { + if (_this.opts.autoUnref) { + _this.ws._socket.unref(); + } + _this.onOpen(); + }; + this.ws.onclose = function (closeEvent) { + return _this.onClose({ + description: "websocket connection closed", + context: closeEvent, + }); + }; + this.ws.onmessage = function (ev) { + return _this.onData(ev.data); + }; + this.ws.onerror = function (e) { + return _this.onError("websocket error", e); + }; + }; + _proto.write = function write(packets) { + var _this2 = 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, _this2.supportsBinary, function (data) { + // 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 { + _this2.doWrite(packet, data); + } catch (e) {} + if (lastPacket) { + // fake drain + // defer to next tick to allow Socket to clear writeBuffer + nextTick(function () { + _this2.writable = true; + _this2.emitReserved("drain"); + }, _this2.setTimeoutFn); + } + }); + }; + for (var i = 0; i < packets.length; i++) { + _loop(); + } + }; + _proto.doClose = function doClose() { + if (typeof this.ws !== "undefined") { + this.ws.close(); + this.ws = null; + } + }; + /** + * Generates uri for connection. + * + * @private + */ + _proto.uri = 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] = randomString(); + } + // communicate binary support capabilities + if (!this.supportsBinary) { + query.b64 = 1; + } + return this.createUri(schema, query); + }; + return _createClass(BaseWS, [ + { + key: "name", + get: function get() { + return "websocket"; + }, + }, + ]); + })(Transport); + var WebSocketCtor = globalThisShim.WebSocket || globalThisShim.MozWebSocket; + /** + * WebSocket transport based on the built-in `WebSocket` object. + * + * Usage: browser, Node.js (since v21), Deno, Bun + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket + * @see https://caniuse.com/mdn-api_websocket + * @see https://nodejs.org/api/globals.html#websocket + */ + var WS = /*#__PURE__*/ (function (_BaseWS) { + function WS() { + return _BaseWS.apply(this, arguments) || this; + } + _inheritsLoose(WS, _BaseWS); + var _proto2 = WS.prototype; + _proto2.createSocket = function createSocket(uri, protocols, opts) { + return !isReactNative + ? protocols + ? new WebSocketCtor(uri, protocols) + : new WebSocketCtor(uri) + : new WebSocketCtor(uri, protocols, opts); + }; + _proto2.doWrite = function doWrite(_packet, data) { + this.ws.send(data); + }; + return WS; + })(BaseWS); + + /** + * WebTransport transport based on the built-in `WebTransport` object. + * + * Usage: browser, Node.js (with the `@fails-components/webtransport` package) + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/WebTransport + * @see https://caniuse.com/webtransport + */ + var WT = /*#__PURE__*/ (function (_Transport) { + function WT() { + return _Transport.apply(this, arguments) || this; + } + _inheritsLoose(WT, _Transport); + var _proto = WT.prototype; + _proto.doOpen = function doOpen() { + var _this = this; + try { + // @ts-ignore + this._transport = new WebTransport( + this.createUri("https"), + this.opts.transportOptions[this.name], + ); + } catch (err) { + return this.emitReserved("error", err); + } + 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(); + }); + }); + }); + }; + _proto.write = 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(); + } + }; + _proto.doClose = function doClose() { + var _a; + (_a = this._transport) === null || _a === void 0 ? void 0 : _a.close(); + }; + return _createClass(WT, [ + { + key: "name", + get: function get() { + return "webtransport"; + }, + }, + ]); + })(Transport); + + var transports = { + websocket: WS, + webtransport: WT, + polling: XHR, + }; + + // 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 (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) { + if (str.length > 8000) { + throw "URI too long"; + } + 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 withEventListeners = + typeof addEventListener === "function" && + typeof removeEventListener === "function"; + var OFFLINE_EVENT_LISTENERS = []; + if (withEventListeners) { + // within a ServiceWorker, any event handler for the 'offline' event must be added on the initial evaluation of the + // script, so we create one single event listener here which will forward the event to the socket instances + addEventListener( + "offline", + function () { + OFFLINE_EVENT_LISTENERS.forEach(function (listener) { + return listener(); + }); + }, + false, + ); + } + /** + * This class provides a WebSocket-like interface to connect to an Engine.IO server. The connection will be established + * with one of the available low-level transports, like HTTP long-polling, WebSocket or WebTransport. + * + * This class comes without upgrade mechanism, which means that it will keep the first low-level transport that + * successfully establishes the connection. + * + * In order to allow tree-shaking, there are no transports included, that's why the `transports` option is mandatory. + * + * @example + * import { SocketWithoutUpgrade, WebSocket } from "engine.io-client"; + * + * const socket = new SocketWithoutUpgrade({ + * transports: [WebSocket] + * }); + * + * socket.on("open", () => { + * socket.send("hello"); + * }); + * + * @see SocketWithUpgrade + * @see Socket + */ + var SocketWithoutUpgrade = /*#__PURE__*/ (function (_Emitter) { + /** + * Socket constructor. + * + * @param {String|Object} uri - uri or options + * @param {Object} opts - options + */ + function SocketWithoutUpgrade(uri, opts) { + var _this; + _this = _Emitter.call(this) || this; + _this.binaryType = defaultBinaryType; + _this.writeBuffer = []; + _this._prevBufferLen = 0; + _this._pingInterval = -1; + _this._pingTimeout = -1; + _this._maxPayload = -1; + /** + * The expiration timestamp of the {@link _pingTimeoutTimer} object is tracked, in case the timer is throttled and the + * callback is not fired on time. This can happen for example when a laptop is suspended or when a phone is locked. + */ + _this._pingTimeoutTime = Infinity; + if (uri && "object" === _typeof(uri)) { + opts = uri; + uri = null; + } + if (uri) { + var parsedUri = parse(uri); + opts.hostname = parsedUri.host; + opts.secure = + parsedUri.protocol === "https" || parsedUri.protocol === "wss"; + opts.port = parsedUri.port; + if (parsedUri.query) opts.query = parsedUri.query; + } else if (opts.host) { + opts.hostname = parse(opts.host).host; + } + installTimerFunctions(_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 = []; + _this._transportsByName = {}; + opts.transports.forEach(function (t) { + var transportName = t.prototype.name; + _this.transports.push(transportName); + _this._transportsByName[transportName] = t; + }); + _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); + } + if (withEventListeners) { + 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", + }); + }; + OFFLINE_EVENT_LISTENERS.push(_this._offlineEventListener); + } + } + if (_this.opts.withCredentials) { + _this._cookieJar = createCookieJar(); + } + _this._open(); + return _this; + } + /** + * Creates transport of the given type. + * + * @param {String} name - transport name + * @return {Transport} + * @private + */ + _inheritsLoose(SocketWithoutUpgrade, _Emitter); + var _proto = SocketWithoutUpgrade.prototype; + _proto.createTransport = 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 this._transportsByName[name](opts); + }; + /** + * Initializes transport to use and starts probe. + * + * @private + */ + _proto._open = function _open() { + var _this2 = this; + if (this.transports.length === 0) { + // Emit error on next tick so it can be listened to + this.setTimeoutFn(function () { + _this2.emitReserved("error", "No transports available"); + }, 0); + return; + } + var transportName = + this.opts.rememberUpgrade && + SocketWithoutUpgrade.priorWebsocketSuccess && + this.transports.indexOf("websocket") !== -1 + ? "websocket" + : this.transports[0]; + this.readyState = "opening"; + var transport = this.createTransport(transportName); + transport.open(); + this.setTransport(transport); + }; + /** + * Sets the current transport. Disables the existing one (if any). + * + * @private + */ + _proto.setTransport = 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); + }); + }; + /** + * Called when connection is deemed open. + * + * @private + */ + _proto.onOpen = function onOpen() { + this.readyState = "open"; + SocketWithoutUpgrade.priorWebsocketSuccess = + "websocket" === this.transport.name; + this.emitReserved("open"); + this.flush(); + }; + /** + * Handles a packet. + * + * @private + */ + _proto._onPacket = 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"); + switch (packet.type) { + case "open": + this.onHandshake(JSON.parse(packet.data)); + break; + case "ping": + this._sendPacket("pong"); + this.emitReserved("ping"); + this.emitReserved("pong"); + this._resetPingTimeout(); + 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 + */ + _proto.onHandshake = function onHandshake(data) { + this.emitReserved("handshake", data); + this.id = data.sid; + this.transport.query.sid = data.sid; + 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 + */ + _proto._resetPingTimeout = function _resetPingTimeout() { + var _this4 = this; + this.clearTimeoutFn(this._pingTimeoutTimer); + var delay = this._pingInterval + this._pingTimeout; + this._pingTimeoutTime = Date.now() + delay; + this._pingTimeoutTimer = this.setTimeoutFn(function () { + _this4._onClose("ping timeout"); + }, delay); + if (this.opts.autoUnref) { + this._pingTimeoutTimer.unref(); + } + }; + /** + * Called on `drain` event + * + * @private + */ + _proto._onDrain = 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 + */ + _proto.flush = 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 + */ + _proto._getWritablePackets = 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; + }; + /** + * Checks whether the heartbeat timer has expired but the socket has not yet been notified. + * + * Note: this method is private for now because it does not really fit the WebSocket API, but if we put it in the + * `write()` method then the message would not be buffered by the Socket.IO client. + * + * @return {boolean} + * @private + */ + /* private */ + _proto._hasPingExpired = function _hasPingExpired() { + var _this5 = this; + if (!this._pingTimeoutTime) return true; + var hasExpired = Date.now() > this._pingTimeoutTime; + if (hasExpired) { + this._pingTimeoutTime = 0; + nextTick(function () { + _this5._onClose("ping timeout"); + }, this.setTimeoutFn); + } + return hasExpired; + }; + /** + * Sends a message. + * + * @param {String} msg - message. + * @param {Object} options. + * @param {Function} fn - callback function. + * @return {Socket} for chaining. + */ + _proto.write = function write(msg, options, fn) { + this._sendPacket("message", msg, options, fn); + return this; + }; + /** + * Sends a message. Alias of {@link Socket#write}. + * + * @param {String} msg - message. + * @param {Object} options. + * @param {Function} fn - callback function. + * @return {Socket} for chaining. + */ + _proto.send = 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 + */ + _proto._sendPacket = 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. + */ + _proto.close = 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 + */ + _proto._onError = function _onError(err) { + SocketWithoutUpgrade.priorWebsocketSuccess = false; + if ( + this.opts.tryAllTransports && + this.transports.length > 1 && + this.readyState === "opening" + ) { + this.transports.shift(); + return this._open(); + } + this.emitReserved("error", err); + this._onClose("transport error", err); + }; + /** + * Called upon transport close. + * + * @private + */ + _proto._onClose = 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 (withEventListeners) { + if (this._beforeunloadEventListener) { + removeEventListener( + "beforeunload", + this._beforeunloadEventListener, + false, + ); + } + if (this._offlineEventListener) { + var i = OFFLINE_EVENT_LISTENERS.indexOf(this._offlineEventListener); + if (i !== -1) { + OFFLINE_EVENT_LISTENERS.splice(i, 1); + } + } + } + // 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; + } + }; + return SocketWithoutUpgrade; + })(Emitter); + SocketWithoutUpgrade.protocol = protocol$1; + /** + * This class provides a WebSocket-like interface to connect to an Engine.IO server. The connection will be established + * with one of the available low-level transports, like HTTP long-polling, WebSocket or WebTransport. + * + * This class comes with an upgrade mechanism, which means that once the connection is established with the first + * low-level transport, it will try to upgrade to a better transport. + * + * In order to allow tree-shaking, there are no transports included, that's why the `transports` option is mandatory. + * + * @example + * import { SocketWithUpgrade, WebSocket } from "engine.io-client"; + * + * const socket = new SocketWithUpgrade({ + * transports: [WebSocket] + * }); + * + * socket.on("open", () => { + * socket.send("hello"); + * }); + * + * @see SocketWithoutUpgrade + * @see Socket + */ + var SocketWithUpgrade = /*#__PURE__*/ (function (_SocketWithoutUpgrade) { + function SocketWithUpgrade() { + var _this7; + _this7 = _SocketWithoutUpgrade.apply(this, arguments) || this; + _this7._upgrades = []; + return _this7; + } + _inheritsLoose(SocketWithUpgrade, _SocketWithoutUpgrade); + var _proto2 = SocketWithUpgrade.prototype; + _proto2.onOpen = function onOpen() { + _SocketWithoutUpgrade.prototype.onOpen.call(this); + if ("open" === this.readyState && this.opts.upgrade) { + for (var i = 0; i < this._upgrades.length; i++) { + this._probe(this._upgrades[i]); + } + } + }; + /** + * Probes a transport. + * + * @param {String} name - transport name + * @private + */ + _proto2._probe = function _probe(name) { + var _this8 = this; + var transport = this.createTransport(name); + var failed = false; + SocketWithoutUpgrade.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) { + _this8.upgrading = true; + _this8.emitReserved("upgrading", transport); + if (!transport) return; + SocketWithoutUpgrade.priorWebsocketSuccess = + "websocket" === transport.name; + _this8.transport.pause(function () { + if (failed) return; + if ("closed" === _this8.readyState) return; + cleanup(); + _this8.setTransport(transport); + transport.send([ + { + type: "upgrade", + }, + ]); + _this8.emitReserved("upgrade", transport); + transport = null; + _this8.upgrading = false; + _this8.flush(); + }); + } else { + var err = new Error("probe error"); + // @ts-ignore + err.transport = transport.name; + _this8.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(); + _this8.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); + _this8.off("close", onclose); + _this8.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(); + } + }; + _proto2.onHandshake = function onHandshake(data) { + this._upgrades = this._filterUpgrades(data.upgrades); + _SocketWithoutUpgrade.prototype.onHandshake.call(this, data); + }; + /** + * Filters upgrades, returning only those matching client transports. + * + * @param {Array} upgrades - server upgrades + * @private + */ + _proto2._filterUpgrades = function _filterUpgrades(upgrades) { + var filteredUpgrades = []; + for (var i = 0; i < upgrades.length; i++) { + if (~this.transports.indexOf(upgrades[i])) + filteredUpgrades.push(upgrades[i]); + } + return filteredUpgrades; + }; + return SocketWithUpgrade; + })(SocketWithoutUpgrade); + /** + * This class provides a WebSocket-like interface to connect to an Engine.IO server. The connection will be established + * with one of the available low-level transports, like HTTP long-polling, WebSocket or WebTransport. + * + * This class comes with an upgrade mechanism, which means that once the connection is established with the first + * low-level transport, it will try to upgrade to a better transport. + * + * @example + * import { Socket } from "engine.io-client"; + * + * const socket = new Socket(); + * + * socket.on("open", () => { + * socket.send("hello"); + * }); + * + * @see SocketWithoutUpgrade + * @see SocketWithUpgrade + */ + var Socket$1 = /*#__PURE__*/ (function (_SocketWithUpgrade) { + function Socket(uri) { + var opts = + arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var o = _typeof(uri) === "object" ? uri : opts; + if ( + !o.transports || + (o.transports && typeof o.transports[0] === "string") + ) { + o.transports = ( + o.transports || ["polling", "websocket", "webtransport"] + ) + .map(function (transportName) { + return transports[transportName]; + }) + .filter(function (t) { + return !!t; + }); + } + return _SocketWithUpgrade.call(this, uri, o) || this; + } + _inheritsLoose(Socket, _SocketWithUpgrade); + return Socket; + })(SocketWithUpgrade); + + Socket$1.protocol; + + function getDefaultExportFromCjs(x) { + return x && + x.__esModule && + Object.prototype.hasOwnProperty.call(x, "default") + ? x["default"] + : x; + } + + var browser = { exports: {} }; + + var ms; + var hasRequiredMs; + function requireMs() { + if (hasRequiredMs) return ms; + hasRequiredMs = 1; + var s = 1000; + var m = s * 60; + var h = m * 60; + var d = h * 24; + var w = d * 7; + var y = d * 365.25; + + /** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} [options] + * @throws {Error} throw an error if val is not a non-empty string or a number + * @return {String|Number} + * @api public + */ + + ms = function ms(val, options) { + options = options || {}; + var type = _typeof(val); + if (type === "string" && val.length > 0) { + return parse(val); + } else if (type === "number" && isFinite(val)) { + return options["long"] ? fmtLong(val) : fmtShort(val); + } + throw new Error( + "val is not a non-empty string or a valid number. val=" + + JSON.stringify(val), + ); + }; + + /** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + + function parse(str) { + str = String(str); + if (str.length > 100) { + return; + } + var match = + /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec( + str, + ); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || "ms").toLowerCase(); + switch (type) { + case "years": + case "year": + case "yrs": + case "yr": + case "y": + return n * y; + case "weeks": + case "week": + case "w": + return n * w; + case "days": + case "day": + case "d": + return n * d; + case "hours": + case "hour": + case "hrs": + case "hr": + case "h": + return n * h; + case "minutes": + case "minute": + case "mins": + case "min": + case "m": + return n * m; + case "seconds": + case "second": + case "secs": + case "sec": + case "s": + return n * s; + case "milliseconds": + case "millisecond": + case "msecs": + case "msec": + case "ms": + return n; + default: + return undefined; + } + } + + /** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + + function fmtShort(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return Math.round(ms / d) + "d"; + } + if (msAbs >= h) { + return Math.round(ms / h) + "h"; + } + if (msAbs >= m) { + return Math.round(ms / m) + "m"; + } + if (msAbs >= s) { + return Math.round(ms / s) + "s"; + } + return ms + "ms"; + } + + /** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + + function fmtLong(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return plural(ms, msAbs, d, "day"); + } + if (msAbs >= h) { + return plural(ms, msAbs, h, "hour"); + } + if (msAbs >= m) { + return plural(ms, msAbs, m, "minute"); + } + if (msAbs >= s) { + return plural(ms, msAbs, s, "second"); + } + return ms + " ms"; + } + + /** + * Pluralization helper. + */ + + function plural(ms, msAbs, n, name) { + var isPlural = msAbs >= n * 1.5; + return Math.round(ms / n) + " " + name + (isPlural ? "s" : ""); + } + return ms; + } + + /** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + */ + + function setup(env) { + createDebug.debug = createDebug; + createDebug["default"] = createDebug; + createDebug.coerce = coerce; + createDebug.disable = disable; + createDebug.enable = enable; + createDebug.enabled = enabled; + createDebug.humanize = requireMs(); + createDebug.destroy = destroy; + Object.keys(env).forEach(function (key) { + createDebug[key] = env[key]; + }); + + /** + * The currently active debug mode names, and names to skip. + */ + + createDebug.names = []; + createDebug.skips = []; + + /** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + createDebug.formatters = {}; + + /** + * Selects a color for a debug namespace + * @param {String} namespace The namespace string for the debug instance to be colored + * @return {Number|String} An ANSI color code for the given namespace + * @api private + */ + function selectColor(namespace) { + var hash = 0; + for (var i = 0; i < namespace.length; i++) { + hash = (hash << 5) - hash + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + return createDebug.colors[Math.abs(hash) % createDebug.colors.length]; + } + createDebug.selectColor = selectColor; + + /** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + function createDebug(namespace) { + var prevTime; + var enableOverride = null; + var namespacesCache; + var enabledCache; + function debug() { + for ( + var _len = arguments.length, args = new Array(_len), _key = 0; + _key < _len; + _key++ + ) { + args[_key] = arguments[_key]; + } + // Disabled? + if (!debug.enabled) { + return; + } + var self = debug; + + // Set `diff` timestamp + var curr = Number(new Date()); + var ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + args[0] = createDebug.coerce(args[0]); + if (typeof args[0] !== "string") { + // Anything else let's inspect with %O + args.unshift("%O"); + } + + // Apply any `formatters` transformations + var index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, function (match, format) { + // If we encounter an escaped % then don't increase the array index + if (match === "%%") { + return "%"; + } + index++; + var formatter = createDebug.formatters[format]; + if (typeof formatter === "function") { + var val = args[index]; + match = formatter.call(self, val); + + // Now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + // Apply env-specific formatting (colors, etc.) + createDebug.formatArgs.call(self, args); + var logFn = self.log || createDebug.log; + logFn.apply(self, args); + } + debug.namespace = namespace; + debug.useColors = createDebug.useColors(); + debug.color = createDebug.selectColor(namespace); + debug.extend = extend; + debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release. + + Object.defineProperty(debug, "enabled", { + enumerable: true, + configurable: false, + get: function get() { + if (enableOverride !== null) { + return enableOverride; + } + if (namespacesCache !== createDebug.namespaces) { + namespacesCache = createDebug.namespaces; + enabledCache = createDebug.enabled(namespace); + } + return enabledCache; + }, + set: function set(v) { + enableOverride = v; + }, + }); + + // Env-specific initialization logic for debug instances + if (typeof createDebug.init === "function") { + createDebug.init(debug); + } + return debug; + } + function extend(namespace, delimiter) { + var newDebug = createDebug( + this.namespace + + (typeof delimiter === "undefined" ? ":" : delimiter) + + namespace, + ); + newDebug.log = this.log; + return newDebug; + } + + /** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + function enable(namespaces) { + createDebug.save(namespaces); + createDebug.namespaces = namespaces; + createDebug.names = []; + createDebug.skips = []; + var i; + var split = (typeof namespaces === "string" ? namespaces : "").split( + /[\s,]+/, + ); + var len = split.length; + for (i = 0; i < len; i++) { + if (!split[i]) { + // ignore empty strings + continue; + } + namespaces = split[i].replace(/\*/g, ".*?"); + if (namespaces[0] === "-") { + createDebug.skips.push(new RegExp("^" + namespaces.slice(1) + "$")); + } else { + createDebug.names.push(new RegExp("^" + namespaces + "$")); + } + } + } + + /** + * Disable debug output. + * + * @return {String} namespaces + * @api public + */ + function disable() { + var namespaces = [] + .concat( + _toConsumableArray(createDebug.names.map(toNamespace)), + _toConsumableArray( + createDebug.skips.map(toNamespace).map(function (namespace) { + return "-" + namespace; + }), + ), + ) + .join(","); + createDebug.enable(""); + return namespaces; + } + + /** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + function enabled(name) { + if (name[name.length - 1] === "*") { + return true; + } + var i; + var len; + for (i = 0, len = createDebug.skips.length; i < len; i++) { + if (createDebug.skips[i].test(name)) { + return false; + } + } + for (i = 0, len = createDebug.names.length; i < len; i++) { + if (createDebug.names[i].test(name)) { + return true; + } + } + return false; + } + + /** + * Convert regexp to namespace + * + * @param {RegExp} regxep + * @return {String} namespace + * @api private + */ + function toNamespace(regexp) { + return regexp + .toString() + .substring(2, regexp.toString().length - 2) + .replace(/\.\*\?$/, "*"); + } + + /** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + function coerce(val) { + if (val instanceof Error) { + return val.stack || val.message; + } + return val; + } + + /** + * XXX DO NOT USE. This is a temporary stub function. + * XXX It WILL be removed in the next major release. + */ + function destroy() { + console.warn( + "Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.", + ); + } + createDebug.enable(createDebug.load()); + return createDebug; + } + var common = setup; + + /* eslint-env browser */ + browser.exports; + (function (module, exports) { + /** + * This is the web browser implementation of `debug()`. + */ + + exports.formatArgs = formatArgs; + exports.save = save; + exports.load = load; + exports.useColors = useColors; + exports.storage = localstorage(); + exports.destroy = (function () { + var warned = false; + return function () { + if (!warned) { + warned = true; + console.warn( + "Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.", + ); + } + }; + })(); + + /** + * Colors. + */ + + exports.colors = [ + "#0000CC", + "#0000FF", + "#0033CC", + "#0033FF", + "#0066CC", + "#0066FF", + "#0099CC", + "#0099FF", + "#00CC00", + "#00CC33", + "#00CC66", + "#00CC99", + "#00CCCC", + "#00CCFF", + "#3300CC", + "#3300FF", + "#3333CC", + "#3333FF", + "#3366CC", + "#3366FF", + "#3399CC", + "#3399FF", + "#33CC00", + "#33CC33", + "#33CC66", + "#33CC99", + "#33CCCC", + "#33CCFF", + "#6600CC", + "#6600FF", + "#6633CC", + "#6633FF", + "#66CC00", + "#66CC33", + "#9900CC", + "#9900FF", + "#9933CC", + "#9933FF", + "#99CC00", + "#99CC33", + "#CC0000", + "#CC0033", + "#CC0066", + "#CC0099", + "#CC00CC", + "#CC00FF", + "#CC3300", + "#CC3333", + "#CC3366", + "#CC3399", + "#CC33CC", + "#CC33FF", + "#CC6600", + "#CC6633", + "#CC9900", + "#CC9933", + "#CCCC00", + "#CCCC33", + "#FF0000", + "#FF0033", + "#FF0066", + "#FF0099", + "#FF00CC", + "#FF00FF", + "#FF3300", + "#FF3333", + "#FF3366", + "#FF3399", + "#FF33CC", + "#FF33FF", + "#FF6600", + "#FF6633", + "#FF9900", + "#FF9933", + "#FFCC00", + "#FFCC33", + ]; + + /** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + + // eslint-disable-next-line complexity + function useColors() { + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if ( + typeof window !== "undefined" && + window.process && + (window.process.type === "renderer" || window.process.__nwjs) + ) { + return true; + } + + // Internet Explorer and Edge do not support colors. + if ( + typeof navigator !== "undefined" && + navigator.userAgent && + navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/) + ) { + return false; + } + + // Is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + return ( + (typeof document !== "undefined" && + document.documentElement && + document.documentElement.style && + document.documentElement.style.WebkitAppearance) || + // Is firebug? http://stackoverflow.com/a/398120/376773 + (typeof window !== "undefined" && + window.console && + (window.console.firebug || + (window.console.exception && window.console.table))) || + // Is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (typeof navigator !== "undefined" && + navigator.userAgent && + navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && + parseInt(RegExp.$1, 10) >= 31) || + // Double check webkit in userAgent just in case we are in a worker + (typeof navigator !== "undefined" && + navigator.userAgent && + navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)) + ); + } + + /** + * Colorize log arguments if enabled. + * + * @api public + */ + + function formatArgs(args) { + args[0] = + (this.useColors ? "%c" : "") + + this.namespace + + (this.useColors ? " %c" : " ") + + args[0] + + (this.useColors ? "%c " : " ") + + "+" + + module.exports.humanize(this.diff); + if (!this.useColors) { + return; + } + var c = "color: " + this.color; + args.splice(1, 0, c, "color: inherit"); + + // The final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + var index = 0; + var lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, function (match) { + if (match === "%%") { + return; + } + index++; + if (match === "%c") { + // We only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + args.splice(lastC, 0, c); + } + + /** + * Invokes `console.debug()` when available. + * No-op when `console.debug` is not a "function". + * If `console.debug` is not available, falls back + * to `console.log`. + * + * @api public + */ + exports.log = console.debug || console.log || function () {}; + + /** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + function save(namespaces) { + try { + if (namespaces) { + exports.storage.setItem("debug", namespaces); + } else { + exports.storage.removeItem("debug"); + } + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } + } + + /** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + function load() { + var r; + try { + r = exports.storage.getItem("debug"); + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } + + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + if (!r && typeof process !== "undefined" && "env" in process) { + r = process.env.DEBUG; + } + return r; + } + + /** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + + function localstorage() { + try { + // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context + // The Browser also has localStorage in the global context. + return localStorage; + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } + } + module.exports = common(exports); + var formatters = module.exports.formatters; + + /** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + + formatters.j = function (v) { + try { + return JSON.stringify(v); + } catch (error) { + return "[UnexpectedJSONParseError]: " + error.message; + } + }; + })(browser, browser.exports); + var browserExports = browser.exports; + var debugModule = /*@__PURE__*/ getDefaultExportFromCjs(browserExports); + + var debug$3 = debugModule("socket.io-client:url"); // debug() + /** + * 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)) { + debug$3("protocol-less url %s", uri); + if ("undefined" !== typeof loc) { + uri = loc.protocol + "//" + uri; + } else { + uri = "https://" + uri; + } + } + // parse + debug$3("parse %s", uri); + 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", + // used on the client side + "connect_error", + // used on the client side + "disconnect", + // used on both sides + "disconnecting", + // used on the server side + "newListener", + // used by the Node.js EventEmitter + "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) { + 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 + */ + var _proto = Encoder.prototype; + _proto.encode = 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. + */ + _proto.encodeAsString = 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. + */ + _proto.encodeAsBinary = 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; + })(); + /** + * A socket.io Decoder instance + * + * @return {Object} decoder + */ + var Decoder = /*#__PURE__*/ (function (_Emitter) { + /** + * Decoder constructor + * + * @param {function} reviver - custom reviver to pass down to JSON.stringify + */ + function Decoder(reviver) { + var _this; + _this = _Emitter.call(this) || this; + _this.reviver = reviver; + return _this; + } + /** + * Decodes an encoded packet string into packet JSON. + * + * @param {String} obj - encoded packet + */ + _inheritsLoose(Decoder, _Emitter); + var _proto2 = Decoder.prototype; + _proto2.add = 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) { + _Emitter.prototype.emitReserved.call(this, "decoded", packet); + } + } else { + // non-binary full packet + _Emitter.prototype.emitReserved.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; + _Emitter.prototype.emitReserved.call(this, "decoded", packet); + } + } + } else { + throw new Error("Unknown type: " + obj); + } + }; + /** + * Decode a packet String (JSON data) + * + * @param {String} str + * @return {Object} packet + */ + _proto2.decodeString = 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; + }; + _proto2.tryParse = function tryParse(str) { + try { + return JSON.parse(str, this.reviver); + } catch (e) { + return false; + } + }; + Decoder.isPayloadValid = 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); + } + }; + /** + * Deallocates a parser's resources + */ + _proto2.destroy = function destroy() { + if (this.reconstructor) { + this.reconstructor.finishedReconstruction(); + this.reconstructor = null; + } + }; + 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) { + 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. + */ + var _proto3 = BinaryReconstructor.prototype; + _proto3.takeBinaryData = 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. + */ + _proto3.finishedReconstruction = function finishedReconstruction() { + this.reconPack = null; + this.buffers = []; + }; + return BinaryReconstructor; + })(); + function isNamespaceValid(nsp) { + return typeof nsp === "string"; + } + // see https://caniuse.com/mdn-javascript_builtins_number_isinteger + var isInteger = + Number.isInteger || + function (value) { + return ( + typeof value === "number" && + isFinite(value) && + Math.floor(value) === value + ); + }; + function isAckIdValid(id) { + return id === undefined || isInteger(id); + } + // 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]"; + } + function isDataValid(type, payload) { + switch (type) { + case PacketType.CONNECT: + return payload === undefined || isObject(payload); + case PacketType.DISCONNECT: + return payload === undefined; + case PacketType.EVENT: + return ( + Array.isArray(payload) && + (typeof payload[0] === "number" || + (typeof payload[0] === "string" && + RESERVED_EVENTS$1.indexOf(payload[0]) === -1)) + ); + case PacketType.ACK: + return Array.isArray(payload); + case PacketType.CONNECT_ERROR: + return typeof payload === "string" || isObject(payload); + default: + return false; + } + } + function isPacketValid(packet) { + return ( + isNamespaceValid(packet.nsp) && + isAckIdValid(packet.id) && + isDataValid(packet.type, packet.data) + ); + } + + var parser = /*#__PURE__*/ Object.freeze({ + __proto__: null, + protocol: protocol, + get PacketType() { + return PacketType; + }, + Encoder: Encoder, + Decoder: Decoder, + isPacketValid: isPacketValid, + }); + + function on(obj, ev, fn) { + obj.on(ev, fn); + return function subDestroy() { + obj.off(ev, fn); + }; + } + + var debug$2 = debugModule("socket.io-client:socket"); // debug() + /** + * 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) { + /** + * `Socket` constructor. + */ + function Socket(io, nsp, opts) { + var _this; + _this = _Emitter.call(this) || 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 + * }); + */ + _inheritsLoose(Socket, _Emitter); + var _proto = Socket.prototype; + /** + * Subscribe to open, close and packet events + * + * @private + */ + _proto.subEvents = 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 + * }); + */ + /** + * "Opens" the socket. + * + * @example + * const socket = io({ + * autoConnect: false + * }); + * + * socket.connect(); + */ + _proto.connect = 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()}. + */ + _proto.open = 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 + */ + _proto.send = 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 + */ + _proto.emit = function emit(ev) { + var _a, _b, _c; + 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++; + debug$2("emitting packet with ack id %d", id); + var ack = args.pop(); + this._registerAckCallback(id, ack); + packet.id = id; + } + var isTransportWritable = + (_b = + (_a = this.io.engine) === null || _a === void 0 + ? void 0 + : _a.transport) === null || _b === void 0 + ? void 0 + : _b.writable; + var isConnected = + this.connected && + !((_c = this.io.engine) === null || _c === void 0 + ? void 0 + : _c._hasPingExpired()); + var discardPacket = this.flags["volatile"] && !isTransportWritable; + if (discardPacket) { + debug$2("discard packet as the transport is not currently writable"); + } else if (isConnected) { + this.notifyOutgoingListeners(packet); + this.packet(packet); + } else { + this.sendBuffer.push(packet); + } + this.flags = {}; + return this; + }; + /** + * @private + */ + _proto._registerAckCallback = 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) { + debug$2("removing packet with ack id %d from the buffer", id); + _this2.sendBuffer.splice(i, 1); + } + } + debug$2("event with ack id %d has timed out after %d ms", id, timeout); + 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 + */ + _proto.emitWithAck = 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 + */ + _proto._addToQueue = 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) { + debug$2( + "packet [%d] is discarded after %d tries", + packet.id, + packet.tryCount, + ); + _this4._queue.shift(); + if (ack) { + ack(err); + } + } + } else { + debug$2("packet [%d] was successfully sent", packet.id); + _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 + */ + _proto._drainQueue = function _drainQueue() { + var force = + arguments.length > 0 && arguments[0] !== undefined + ? arguments[0] + : false; + debug$2("draining queue"); + if (!this.connected || this._queue.length === 0) { + return; + } + var packet = this._queue[0]; + if (packet.pending && !force) { + debug$2( + "packet [%d] has already been sent and is waiting for an ack", + packet.id, + ); + return; + } + packet.pending = true; + packet.tryCount++; + debug$2("sending packet [%d] (try n°%d)", packet.id, packet.tryCount); + this.flags = packet.flags; + this.emit.apply(this, packet.args); + }; + /** + * Sends a packet. + * + * @param packet + * @private + */ + _proto.packet = function packet(_packet) { + _packet.nsp = this.nsp; + this.io._packet(_packet); + }; + /** + * Called upon engine `open`. + * + * @private + */ + _proto.onopen = function onopen() { + var _this5 = this; + debug$2("transport is open - connecting"); + 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 + */ + _proto._sendConnectPacket = 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 + */ + _proto.onerror = function onerror(err) { + if (!this.connected) { + this.emitReserved("connect_error", err); + } + }; + /** + * Called upon engine `close`. + * + * @param reason + * @param description + * @private + */ + _proto.onclose = function onclose(reason, description) { + debug$2("close (%s)", reason); + 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 + */ + _proto._clearAcks = 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 + */ + _proto.onpacket = 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 + */ + _proto.onevent = function onevent(packet) { + var args = packet.data || []; + debug$2("emitting event %j", args); + if (null != packet.id) { + debug$2("attaching ack callback to event"); + args.push(this.ack(packet.id)); + } + if (this.connected) { + this.emitEvent(args); + } else { + this.receiveBuffer.push(Object.freeze(args)); + } + }; + _proto.emitEvent = 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(); + } + } + _Emitter.prototype.emit.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 + */ + _proto.ack = 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]; + } + debug$2("sending ack %j", args); + self.packet({ + type: PacketType.ACK, + id: id, + data: args, + }); + }; + }; + /** + * Called upon a server acknowledgement. + * + * @param packet + * @private + */ + _proto.onack = function onack(packet) { + var ack = this.acks[packet.id]; + if (typeof ack !== "function") { + debug$2("bad ack %s", packet.id); + return; + } + delete this.acks[packet.id]; + debug$2("calling ack %s with %j", packet.id, packet.data); + // @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 + */ + _proto.onconnect = function onconnect(id, pid) { + debug$2("socket connected with id %s", id); + 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 + */ + _proto.emitBuffered = 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 + */ + _proto.ondisconnect = function ondisconnect() { + debug$2("server disconnect (%s)", this.nsp); + 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 + */ + _proto.destroy = 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 + */ + _proto.disconnect = function disconnect() { + if (this.connected) { + debug$2("performing disconnect (%s)", this.nsp); + 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 + */ + _proto.close = 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 + */ + _proto.compress = 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 + */ + /** + * 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 + */ + _proto.timeout = 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 + */ + _proto.onAny = 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 + */ + _proto.prependAny = 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 + */ + _proto.offAny = 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. + */ + _proto.listenersAny = 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 + */ + _proto.onAnyOutgoing = 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 + */ + _proto.prependAnyOutgoing = 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) + */ + _proto.offAnyOutgoing = 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. + */ + _proto.listenersAnyOutgoing = function listenersAnyOutgoing() { + return this._anyOutgoingListeners || []; + }; + /** + * Notify the listeners for each packet sent + * + * @param packet + * + * @private + */ + _proto.notifyOutgoingListeners = 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 _createClass(Socket, [ + { + key: "disconnected", + get: function get() { + return !this.connected; + }, + }, + { + key: "active", + get: function get() { + return !!this.subs; + }, + }, + { + key: "volatile", + get: function get() { + this.flags["volatile"] = true; + return this; + }, + }, + ]); + })(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 debug$1 = debugModule("socket.io-client:manager"); // debug() + var Manager = /*#__PURE__*/ (function (_Emitter) { + function Manager(uri, opts) { + var _this; + var _a; + _this = _Emitter.call(this) || 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(_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; + } + _inheritsLoose(Manager, _Emitter); + var _proto = Manager.prototype; + _proto.reconnection = function reconnection(v) { + if (!arguments.length) return this._reconnection; + this._reconnection = !!v; + if (!v) { + this.skipReconnect = true; + } + return this; + }; + _proto.reconnectionAttempts = function reconnectionAttempts(v) { + if (v === undefined) return this._reconnectionAttempts; + this._reconnectionAttempts = v; + return this; + }; + _proto.reconnectionDelay = 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; + }; + _proto.randomizationFactor = 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; + }; + _proto.reconnectionDelayMax = 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; + }; + _proto.timeout = 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 + */ + _proto.maybeReconnectOnOpen = 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 + */ + _proto.open = function open(fn) { + var _this2 = this; + debug$1("readyState %s", this._readyState); + if (~this._readyState.indexOf("open")) return this; + debug$1("opening %s", this.uri); + 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) { + debug$1("error"); + _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; + debug$1("connect attempt will timeout after %d", timeout); + // set timer + var timer = this.setTimeoutFn(function () { + debug$1("connect attempt timed out after %d", timeout); + 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 + */ + _proto.connect = function connect(fn) { + return this.open(fn); + }; + /** + * Called upon transport open. + * + * @private + */ + _proto.onopen = function onopen() { + debug$1("open"); + // 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)), + // @ts-ignore + on(this.decoder, "decoded", this.ondecoded.bind(this)), + ); + }; + /** + * Called upon a ping. + * + * @private + */ + _proto.onping = function onping() { + this.emitReserved("ping"); + }; + /** + * Called with data. + * + * @private + */ + _proto.ondata = function ondata(data) { + try { + this.decoder.add(data); + } catch (e) { + this.onclose("parse error", e); + } + }; + /** + * Called when parser fully decodes a packet. + * + * @private + */ + _proto.ondecoded = 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 + */ + _proto.onerror = function onerror(err) { + debug$1("error", err); + this.emitReserved("error", err); + }; + /** + * Creates a new socket for the given `nsp`. + * + * @return {Socket} + * @public + */ + _proto.socket = 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 + */ + _proto._destroy = 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) { + debug$1("socket %s is still active, skipping close", nsp); + return; + } + } + this._close(); + }; + /** + * Writes a packet. + * + * @param packet + * @private + */ + _proto._packet = function _packet(packet) { + debug$1("writing packet %j", 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 + */ + _proto.cleanup = function cleanup() { + debug$1("cleanup"); + this.subs.forEach(function (subDestroy) { + return subDestroy(); + }); + this.subs.length = 0; + this.decoder.destroy(); + }; + /** + * Close the current socket. + * + * @private + */ + _proto._close = function _close() { + debug$1("disconnect"); + this.skipReconnect = true; + this._reconnecting = false; + this.onclose("forced close"); + }; + /** + * Alias for close() + * + * @private + */ + _proto.disconnect = function disconnect() { + return this._close(); + }; + /** + * Called when: + * + * - the low-level engine is closed + * - the parser encountered a badly formatted packet + * - all sockets are disconnected + * + * @private + */ + _proto.onclose = function onclose(reason, description) { + var _a; + debug$1("closed due to %s", reason); + this.cleanup(); + (_a = this.engine) === null || _a === void 0 ? void 0 : _a.close(); + this.backoff.reset(); + this._readyState = "closed"; + this.emitReserved("close", reason, description); + if (this._reconnection && !this.skipReconnect) { + this.reconnect(); + } + }; + /** + * Attempt a reconnection. + * + * @private + */ + _proto.reconnect = function reconnect() { + var _this4 = this; + if (this._reconnecting || this.skipReconnect) return this; + var self = this; + if (this.backoff.attempts >= this._reconnectionAttempts) { + debug$1("reconnect failed"); + this.backoff.reset(); + this.emitReserved("reconnect_failed"); + this._reconnecting = false; + } else { + var delay = this.backoff.duration(); + debug$1("will wait %dms before reconnect attempt", delay); + this._reconnecting = true; + var timer = this.setTimeoutFn(function () { + if (self.skipReconnect) return; + debug$1("attempting reconnect"); + _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) { + debug$1("reconnect attempt error"); + self._reconnecting = false; + self.reconnect(); + _this4.emitReserved("reconnect_error", err); + } else { + debug$1("reconnect success"); + self.onreconnect(); + } + }); + }, delay); + if (this.opts.autoUnref) { + timer.unref(); + } + this.subs.push(function () { + _this4.clearTimeoutFn(timer); + }); + } + }; + /** + * Called upon successful reconnect. + * + * @private + */ + _proto.onreconnect = function onreconnect() { + var attempt = this.backoff.attempts; + this._reconnecting = false; + this.backoff.reset(); + this.emitReserved("reconnect", attempt); + }; + return Manager; + })(Emitter); + + var debug = debugModule("socket.io-client"); // debug() + /** + * 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) { + debug("ignoring socket cache for %s", source); + io = new Manager(source, opts); + } else { + if (!cache[id]) { + debug("new io instance for %s", source); + 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; +}); diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..8d5dde2 --- /dev/null +++ b/static/style.css @@ -0,0 +1,162 @@ +@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap'); + +:root { + --bg: #334; + --bg-2: #557; + --fg: #fff; + --bg-chat-service: #ccc; + --fg-chat-service: #000; + --bg-chat-self: #fff; + --fg-chat-self: #000; + --bg-chat-other: #335; + --fg-chat-other: #fff; + --bg-cell-open: #eee; + --bg-cell-closed: #adf; + --bg-cell-bomb: #f55; + --fg-cell-1: #6B9EFF; + --fg-cell-2: #6BCF8A; + --fg-cell-3: #FF7B7B; + --fg-cell-4: #6B7BFF; + --fg-cell-5: #C79B6B; + --fg-cell-6: #6BD1C7; + --fg-cell-7: #666; + --fg-cell-8: #999; +} + +* { + margin: 0; + box-sizing: border-box; +} + +body { + background-color: var(--bg); + color: var(--fg); + font-family: "Montserrat", sans-serif; + font-optical-sizing: auto; +} + +#content { + margin: 50px; + gap: 10px; + display: flex; + flex-direction: column; + justify-content: center; +} + +.block { + display: flex; + flex-direction: column; + gap: 10px; +} + +.block>div { + display: flex; + flex: 1 1 auto; + gap: 5px; +} + +.block>div>* { + flex: 1 1 0; + text-align: center; +} + +#game { + background: #fff; + + border: 1px solid var(--bg); + + display: flex; + + border-radius: 10px; + + overflow: hidden; +} + +#game, +#game-canvas { + height: 600px; +} + +#game-canvas { + border-right: 1px solid var(--bg); + background-color: var(--bg-2); + width: 800px; +} + +#game-tools { + background-color: var(--bg-2); + display: flex; + flex-direction: column; + flex: 1 1 auto; +} + +#chat-and-users { + border-bottom: 1px solid var(--bg); + flex: 1 1 0; + display: flex; + overflow: hidden; +} + +#chat-and-users>* { + flex: 1 1 0; + /* display: flex; + flex-direction: column; */ + + /* justify-content: flex-end; */ + overflow: auto; +} + +.chat-service-msg, +.chat-self-msg, +.chat-other-msg { + border-radius: 5px; + padding: 5px; + margin: 5px; + /* width: 100%; */ + overflow: hidden; +} + +.chat-service-msg { + background-color: var(--bg-chat-service); + color: var(--fg-chat-service); +} + +.chat-self-msg { + background-color: var(--bg-chat-self); + color: var(--fg-chat-self); +} + +.chat-other-msg { + background-color: var(--bg-chat-other); + color: var(--fg-chat-other); +} + +#buttons { + display: flex; +} + +#buttons>input { + flex: 1 1 auto; +} + +.color-picker { + display: flex; + align-items: center; +} + +.color-picker>span { + flex: 1 1 auto; + text-align: center; + color: var(--fg); +} + +a { + color: var(--fg); +} + +footer { + font-size: 1.5em; + margin-top: 100px; + font-family: 500; + text-align: center; +} \ No newline at end of file