diff --git a/api/.gitignore b/api/.gitignore new file mode 100644 index 0000000..8d81f64 --- /dev/null +++ b/api/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +api.zip \ No newline at end of file diff --git a/api/.mtimes.json b/api/.mtimes.json new file mode 100644 index 0000000..30f113b --- /dev/null +++ b/api/.mtimes.json @@ -0,0 +1 @@ +{".gitignore": 1741162450.0110252, "api.py": 1757749878.4958198, "apiBlueprint.py": 1757238077.9332085, "certs.py": 1757228708.4552765, "config.json": 1756001769.9660056, "eventsSocketio.py": 1757328543.5948038, "requirements.txt": 1766641471.4877045, "__pycache__\\api.cpython-311.pyc": 1739790368.2421517} \ No newline at end of file diff --git a/api/api.py b/api/api.py new file mode 100644 index 0000000..be202bf --- /dev/null +++ b/api/api.py @@ -0,0 +1,110 @@ +import os +from pathlib import Path + +from flask import Flask, Blueprint, request, send_from_directory, jsonify +from flask_cors import CORS, cross_origin +from flask_socketio import SocketIO, Namespace + +from libs.app.common.logging import get_logger +from libs.app.common.paths import ROOT_DIR +from libs.fspn.utils.wrapper_util import threaded +from .apiBlueprint import ApiBlueprint +from .eventsSocketio import EventsSocketio +from libs.noSys.noSysModule import NoSysModule +from .certs import generate_ca_and_cert, add_ca_os + +logger = get_logger() + +class Api(NoSysModule): + def __init__(self, nosys_core): + super().__init__(nosys_core) + + self.dist_dir = os.path.join(ROOT_DIR, "libs/vueNoSys/dist") + self.assets_dir = os.path.join(self.dist_dir, "assets") + self.app = Flask(__name__, static_folder=self.assets_dir, template_folder=self.dist_dir) + CORS(self.app, resources={r"/*": {"origins": "*"}}, supports_credentials=True) + self.socketio = SocketIO(self.app, cors_allowed_origins="*") + self.server = None + + self.host = self.config["server"]["host"] + self.port = self.config["server"]["port"] + + self.register_blueprint(BasicBlueprint(self).blueprint) + self.register_socketio(BasicEventSocketIo(self)) + + def setup(self): + self.nosys_core.modules.api = self + + certs_path = os.path.join(ROOT_DIR, "libs", "api", "certs") + self.ca_path = os.path.join(certs_path , "ca.pem") + self.ca_key_path = os.path.join(certs_path, "ca_key.pem") + self.cert_path = os.path.join(certs_path, "cert.pem") + self.key_path = os.path.join(certs_path, "key.pem") + + if not os.path.exists(self.cert_path) or not os.path.exists(self.key_path) or not os.path.exists(self.ca_path) or not os.path.exists(self.ca_key_path): + Path(certs_path).mkdir(parents=True, exist_ok=True) + logger.debug("Generating certs") + ca, cert, key = generate_ca_and_cert(self.ca_path, self.ca_key_path, self.cert_path, self.key_path) + logger.debug("Adding ca to operational system") + add_ca_os(self.ca_path) + logger.debug("Cert installed") + else: + logger.debug("Certs already exists") + + def register_blueprint(self, blueprint:Blueprint): + try: + self.app.register_blueprint(blueprint) + logger.debug(f"Registered blueprint {blueprint.url_prefix}") + except Exception: + logger.exception(f"Failed registering blueprint {blueprint.url_prefix}") + + def register_socketio(self, handler:EventsSocketio): + try: + handler.register_events(self.socketio) + logger.debug(f"Registered socketio {handler.namespace}") + except Exception: + logger.exception(f"Failed registering socketio {handler.namespace}") + + def on_nosys_ready(self, event): + self.run() + + @threaded + def run(self): + self.routes() + logger.debug(f'Running Flask API ({self.host}:{self.port}) with urls: {self.app.url_map}') + try: + self.socketio.run(app=self.app, host=self.host, port=self.port, allow_unsafe_werkzeug=True, ssl_context=(self.cert_path, self.key_path)) + except Exception as e: + logger.error(e) + + def routes(self): + @self.app.route("/", defaults={"path": ""}) + @self.app.route("/") + def index(path): + if path != "" and os.path.exists(os.path.join(self.dist_dir, path)): + return send_from_directory(self.dist_dir, path) + return send_from_directory(self.dist_dir, "index.html") + +class BasicBlueprint(ApiBlueprint): + def routes(self): + self.api:Api = self.module + + @self.blueprint.route('/') + def show(): + return "API" + +class BasicEventSocketIo(EventsSocketio): + def events(self): + @self.on("connect") + def on_connect(*args, **kwargs): + print('API connected',args, kwargs, request.sid) + self.emit("welcome", {"msg": f"Your id {request.sid}"}) + + @self.on("disconnect") + def on_disconnect(*args, **kwargs): + print('API disconnected',args, kwargs, request.sid) + + @self.on("message") + def on_message(*args, **kwargs): + print('API Message',args, kwargs) + self.emit("message", f"Message received {args[0]}") \ No newline at end of file diff --git a/api/apiBlueprint.py b/api/apiBlueprint.py new file mode 100644 index 0000000..f6ee503 --- /dev/null +++ b/api/apiBlueprint.py @@ -0,0 +1,26 @@ +from flask import Blueprint, jsonify +from libs.noSys.noSysModule import NoSysModule +from libs.noSys.events import Events +from flask_socketio import SocketIO + +class ApiBlueprint(): + def __init__(self, nosys_module:NoSysModule): + self.module = nosys_module + self.blueprint = Blueprint(self.module.name, __name__, url_prefix='/api/'+self.module.package_id) + + self.default_routes() + self.routes() + + def default_routes(self): + @self.blueprint.route('/health') + def health_check(): + body = {"package":self.module.package_id, "moduleName":self.module.module_id} + return jsonify(body) + + @self.blueprint.route('/config') + def config(): + body = self.module.nosys_core.config.get(self.module.package_id) + return jsonify(body) + + def routes(self): + pass \ No newline at end of file diff --git a/api/certs.py b/api/certs.py new file mode 100644 index 0000000..f6b502d --- /dev/null +++ b/api/certs.py @@ -0,0 +1,115 @@ +import os +import webview +import ssl +import ipaddress +import pathlib +from cryptography import x509 +from cryptography.x509.oid import NameOID +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from datetime import datetime, timedelta +import os +import platform +import subprocess + +def generate_ca_and_cert(ca_path="ca.pem", ca_key_path="ca_key.pem", + cert_path="cert.pem", key_path="key.pem"): + + ca_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + ca_subject = x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"NoSys-CA"), + x509.NameAttribute(NameOID.COMMON_NAME, u"NoSys Local CA"), + ]) + ca_cert = ( + x509.CertificateBuilder() + .subject_name(ca_subject) + .issuer_name(ca_subject) + .public_key(ca_key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(datetime.utcnow()) + .not_valid_after(datetime.utcnow() + timedelta(days=3650)) + .add_extension( + x509.BasicConstraints(ca=True, path_length=None), critical=True, + ) + .sign(ca_key, hashes.SHA256()) + ) + + with open(ca_path, "wb") as f: + f.write(ca_cert.public_bytes(serialization.Encoding.PEM)) + with open(ca_key_path, "wb") as f: + f.write(ca_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + )) + + key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + subject = x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"NoSys"), + x509.NameAttribute(NameOID.COMMON_NAME, u"localhost"), + ]) + cert = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(ca_subject) + .public_key(key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(datetime.utcnow()) + .not_valid_after(datetime.utcnow() + timedelta(days=3650)) + .add_extension( + x509.SubjectAlternativeName([ + x509.DNSName(u"localhost"), + x509.IPAddress(ipaddress.IPv4Address("127.0.0.1"))]), + critical=False, + ) + .sign(ca_key, hashes.SHA256()) + ) + + with open(cert_path, "wb") as f: + f.write(cert.public_bytes(serialization.Encoding.PEM)) + with open(key_path, "wb") as f: + f.write(key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + )) + + return ca_path, cert_path, key_path + +def add_ca_os(ca_path="ca.pem"): + system = platform.system() + if system == "Windows": + add_ca_windows(ca_path) + elif system == "Darwin": + add_ca_macos(ca_path) + elif system == "Linux": + add_ca_linux(ca_path) + else: + raise Exception("Operational system not supported") + +def add_ca_windows(ca_path="ca.pem"): + subprocess.run([ + "powershell", + "-Command", + f'Import-Certificate -FilePath "{os.path.abspath(ca_path)}" -CertStoreLocation Cert:\\CurrentUser\\Root' + ], check=True) + +def add_ca_macos(ca_path="ca.pem"): + subprocess.run([ + "sudo", + "security", + "add-trusted-cert", + "-d", + "-r", "trustRoot", + "-k", "/Library/Keychains/System.keychain", + os.path.abspath(ca_path) + ], check=True) + +def add_ca_linux(ca_path="ca.pem"): + import shutil + dest = "/usr/local/share/ca-certificates/zecho-ca.crt" + shutil.copy(os.path.abspath(ca_path), dest) + subprocess.run(["sudo", "update-ca-certificates"], check=True) + diff --git a/api/config.json b/api/config.json new file mode 100644 index 0000000..e6476ea --- /dev/null +++ b/api/config.json @@ -0,0 +1,6 @@ +{ + "server": { + "host": "127.0.0.1", + "port": "5050" + } +} \ No newline at end of file diff --git a/api/eventsSocketio.py b/api/eventsSocketio.py new file mode 100644 index 0000000..7fbc033 --- /dev/null +++ b/api/eventsSocketio.py @@ -0,0 +1,51 @@ +from datetime import datetime +import time + +from flask import Blueprint, make_response, request, jsonify, session, has_request_context +from flask_socketio import SocketIO, Namespace, emit, send, join_room, leave_room + +from libs.noSys.noSysModule import NoSysModule + +class EventsSocketio(): + def __init__(self, module:NoSysModule): + self.module = module + self.namespace = f"/ws/{self.module.name}" + self.socketio:SocketIO = None + + def register_events(self, socketio:SocketIO): + self.socketio = socketio + self.default_events() + self.events() + + def default_events(self): + @self.socketio.on("health", namespace=self.namespace) + def on_health(*args, **kwargs): + self.emit("health", {"status": "ok"}) + + @self.socketio.on("ping", namespace=self.namespace) + def on_ping(data=None): + self.emit("pong", {"ts": time.time(), "echo": data}) + + + def emit(self, event:str, data=None, room=None, **kwargs): + target = None + if room: + target = room + elif has_request_context(): + target = request.sid + + self.socketio.emit(event, data, to=target, namespace=self.namespace, **kwargs) + + def on(self, event: str): + def decorator(handler): + @self.socketio.on(event, namespace=self.namespace) + def wrapper(*args, **kwargs): + try: + return handler(*args, **kwargs) + except Exception as e: + self.error(str(e)) + return wrapper + return decorator + + def events(self): + pass \ No newline at end of file diff --git a/api/info.json b/api/info.json new file mode 100644 index 0000000..cd4c33a --- /dev/null +++ b/api/info.json @@ -0,0 +1,10 @@ +{ + "id": "api", + "version": 0.042, + "modules": [ + { + "id": "api", + "version": 0 + } + ] +} \ No newline at end of file diff --git a/api/requirements.txt b/api/requirements.txt new file mode 100644 index 0000000..1ac720c --- /dev/null +++ b/api/requirements.txt @@ -0,0 +1,4 @@ +flask +flask-socketio +python-socketio[client] +flask_cors \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..ea631f7 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,4 @@ +__pycache__ +.venv +*.log +app.zip \ No newline at end of file diff --git a/app/.mtimes.json b/app/.mtimes.json new file mode 100644 index 0000000..a515db8 --- /dev/null +++ b/app/.mtimes.json @@ -0,0 +1 @@ +{".gitignore": 1765697926.2944038, "config.json": 1766641092.7797186, "main.py": 1757275665.3581765, "requirements.txt": 1757272908.932603, "start.py": 1767345275.3860576, "updater.py": 1756702920.6384296, "utilss.py": 1755864608.0, "BKP\\.gitignore": 1741162465.633492, "BKP\\config.json": 1755418661.1762836, "BKP\\main.py": 1756113491.7506, "BKP\\start.py": 1752932509.1119049, "BKP\\updater.py": 1752758398.9098537, "BKP\\utils.py": 1752930900.679567, "common\\args.py": 1755902334.0, "common\\config.py": 1756632053.520828, "common\\logging.py": 1756701444.0262084, "common\\network_utils.py": 1756111641.1188223, "common\\paths.py": 1756118190.209198, "common\\process.py": 1757143858.1563985, "common\\store.py": 1757582915.4349658, "common\\__pycache__\\args.cpython-311.pyc": 1755902456.0, "common\\__pycache__\\config.cpython-311.pyc": 1755909410.0, "common\\__pycache__\\logging.cpython-311.pyc": 1756014259.7862537, "common\\__pycache__\\paths.cpython-311.pyc": 1756014259.7917824, "common\\__pycache__\\process.cpython-311.pyc": 1755865508.0, "ui\\ui_server.py": 1757275891.3673892, "ui\\static\\main.js": 1757241125.1477273, "ui\\templates\\index.html": 1756627053.1326618, "__pycache__\\updater.cpython-311.pyc": 1755912104.0, "__pycache__\\utils.cpython-311.pyc": 1753049185.946803} \ No newline at end of file diff --git a/app/BKP/.gitignore b/app/BKP/.gitignore new file mode 100644 index 0000000..ea631f7 --- /dev/null +++ b/app/BKP/.gitignore @@ -0,0 +1,4 @@ +__pycache__ +.venv +*.log +app.zip \ No newline at end of file diff --git a/app/BKP/config.json b/app/BKP/config.json new file mode 100644 index 0000000..d44e7f2 --- /dev/null +++ b/app/BKP/config.json @@ -0,0 +1,75 @@ +{ + "app":{ + "id":"app", + "checkUpdates": true, + "repositories":["http://error/data"], + "version": 0 + }, + "main":{ + "terminalWindow": true + }, + "passwordManager":{ + "package": "lockbox", + "client": "lockboxClient" + }, + "updater":{ + "autoUpdate": true, + "checkUpdates": true, + "repositories":[ + "http://n0sys.duckdns.org:40404/libs" + ] + }, + "packages":[ + { + "id":"fspn", + "checkUpdates": true, + "repositories":["http://error/data"], + "version": 0 + }, + { + "id":"noSys", + "checkUpdates": true + }, + { + "id":"fileTransfer", + "checkUpdates": true + }, + { + "id":"api", + "checkUpdates": true, + "server": { + "host": "127.0.0.1", + "port": "5050" + } + }, + { + "id":"vueNoSys", + "checkUpdates": true, + "server": { + "host": "127.0.0.1", + "port": "3001" + } + }, + { + "id":"lockbox", + "checkUpdates": true, + "servers": { + "api": { + "port": "5001" + } + } + }, + { + "id":"rendezvous", + "checkUpdates": true, + "server": { + "host": "0.0.0.0", + "port": 40441 + } + }, + { + "id":"p2post", + "checkUpdates": true + } + ] +} \ No newline at end of file diff --git a/app/BKP/info.json b/app/BKP/info.json new file mode 100644 index 0000000..7888f47 --- /dev/null +++ b/app/BKP/info.json @@ -0,0 +1,5 @@ +{ + "id":"app", + "version":0, + "modules":[] +} \ No newline at end of file diff --git a/app/BKP/main.py b/app/BKP/main.py new file mode 100644 index 0000000..ea75c80 --- /dev/null +++ b/app/BKP/main.py @@ -0,0 +1,70 @@ +import os, sys +import time +import logging +import traceback + +# TODO NOT SURE IF SYS PATH IS A GOOD PRACTICE +root_dir = os.path.normpath(__file__.split("libs")[0]) +sys.path.append(root_dir) + +from libs.app.utils import Utils, kargs_to_array, get_logger, is_http_running, config_root_logger + +logger = None +try: + config_root_logger() + logger = get_logger() + utils = Utils() +except Exception: + logging.exception("ERROR") + +from libs.app.updater import Updater + +def main(): + log_ascii_art() + logger.debug(f"Readable params: {kargs_to_array()}") + + if not is_noSys_running(): + updater = Updater(utils) + utils.configs.read_packages() + + from libs.noSys.noSysCore import NoSysCore + noSys = NoSysCore(updater) + else: + logger.debug(f"NoSys already running") + # TODO open url frontend + + logger.info(f"---------------- Main ended ----------------") + +def is_noSys_running(): + try: + api_config = utils.configs.packages["api"].config + api_port = api_config["server"]["port"] + api_health_check_url = f"http://localhost:{api_port}/api/health" + logger.debug(f"Checking url: {api_health_check_url}") + return is_http_running(api_health_check_url) + except Exception: + logger.exception("ERROR") + +def log_ascii_art(): + logger.info(""" + _ __ ____ + / |/ /__ / __/_ _____ + / / _ \ _\ \/ // (_-< + /_/|_/\___/ /___/\_, /___/ + /___/ + + Every man must have freedom, must have the scope to form, test, and act upon his own choices, for any sort of development of his own personality to take place. He must, in short, be free in order that he may be fully human. + - Murray Rothbard + + """) + +if __name__ == '__main__': + try: + main() + time.sleep(30) + except Exception: + logger.exception("ERROR") + for sec in range(30): + logger.info(f"Closing in {30-sec}") + time.sleep(1) + diff --git a/app/BKP/start.py b/app/BKP/start.py new file mode 100644 index 0000000..bdefa4b --- /dev/null +++ b/app/BKP/start.py @@ -0,0 +1,59 @@ +import os, sys +import logging +import shutil +import pathlib +import urllib.request +import time +from pathlib import Path +import zipfile + +root_dir = pathlib.Path(__file__).parent.resolve() +logger = logging.getLogger("start") + +def start(): + global logger + logger.debug(f"---------------- START ----------------") + logger.debug(f"Root path: {root_dir}") + + pathlib.Path(os.path.join(root_dir, "libs")).mkdir(parents=True, exist_ok=True) + download_app() + from libs.app.utils import Utils, get_logger, create_venv, download_file, kargs_to_array + create_venv() + args = kargs_to_array() + # args.append("updateApp=False") # NEVER SET TRUE + # args.append("updateLibs=True") + utils = Utils() + + main_file_path = os.path.join(root_dir, "libs","app", "main.py") + pid = utils.new_python_process(main_file_path, args) + logger.info(f"Main process running. PID {pid} - Args: {args}") + logger.info(f"---------------- END ----------------") + +def download_app(): + url = f"{default_repository}/app/app.zip" + path = os.path.join(root_dir, "libs", "app.zip") + if(not os.path.exists(path)): + logger.debug(f"Downloading from URL: {url} to {path}") + urllib.request.urlretrieve(url, path) + logger.debug(f"Extracting all from {path}") + with zipfile.ZipFile(path, 'r') as zip_ref: + zip_ref.extractall(Path(path).parent.absolute()) + + + +default_repository = "http://n0sys.duckdns.org:30303/libs" + +logger.addHandler(logging.StreamHandler()) +app_logs_path = os.path.join(root_dir, "logs", "app") +shutil.rmtree(os.path.join(root_dir, "logs"), ignore_errors=True) +pathlib.Path(app_logs_path).mkdir(parents=True, exist_ok=True) +logger.addHandler(logging.FileHandler(os.path.join(app_logs_path,'start.log'), mode="a", encoding="utf-8")) +logger.setLevel(logging.DEBUG) + +if __name__ == '__main__': + try: + start() + except Exception as e: + logger.exception("ERROR") + time.sleep(30) + \ No newline at end of file diff --git a/app/BKP/updater.py b/app/BKP/updater.py new file mode 100644 index 0000000..2ea6eb5 --- /dev/null +++ b/app/BKP/updater.py @@ -0,0 +1,124 @@ +import sys +import subprocess +import urllib.parse +import urllib.request +import os +from pathlib import Path +import pathlib +import time +import json +import logging +import zipfile +from threading import Thread + +from libs.app.utils import Utils, Package, get_logger, root_dir + +logger = get_logger("updater") + +class Updater(): + def __init__(self, utils:Utils): + self.utils = utils + self.libs_path = os.path.join(root_dir, 'libs') + self.start() + + def start(self): + logger.info(f"---------------- Updater Started ----------------") + # Create better validation code + if("updateApp" in self.utils.kargs): + if(self.utils.kargs["updateApp"]): + self.update_app() + elif(self.utils.configs.data["updater"]["autoUpdate"]): + self.update_app() + + if("restart" in self.utils.flags and self.utils.flags["restart"]): + self.utils.kargs["updateApp"] = False + self.utils.restart_app() + + if("updateLibs" in self.utils.kargs): + if(self.utils.kargs["updateLibs"]): + self.update_libs() + elif(self.utils.configs.data["updater"]["checkUpdates"]): + self.update_libs() + + logger.info(f"---------------- Updater Ended ----------------") + + def update_app(self): + latest_version_repository = (self.utils.configs.data["app"]["version"],None) + logger.info(f"App current version: {latest_version_repository[0]}") + + logger.info(f"Updating app files") + # TODO FIX THIS + self.check_package_update(Package(config=self.utils.configs.data["app"])) + + logger.info(f"App updated to version {latest_version_repository[0]}") + self.utils.flags["restart"] = True + + def update_libs(self): + logger.info(f"Checking packages update") + threads:list[Thread] = [] + for package in self.utils.configs.packages.values(): + # Default is check updates + if(not "checkUpdates" in package.config or package.config["checkUpdates"]): + thread = Thread(target=self.check_package_update, args=(package,)) + thread.start() + threads.append(thread) + + for thread in threads: + thread.join() + + def check_package_update(self, package:Package): + logger.info(f"Updating package {package.config['id']}") + package_id = package.config['id'] + + package_path = os.path.join(self.libs_path, package_id) + if(package.info): + latest_version_repository = (package.info["version"],None) + else: + logger.info(f"Creating package directory: {package_path}") + Path(package_path).mkdir(parents=True, exist_ok=True) + latest_version_repository = (-1,None) + + repositories = self.utils.configs.data["updater"]["repositories"][:] + if "repositories" in package.config: + for repository in package.config["repositories"]: + repositories.append(repository) + + for repository in repositories: + try: + package_url = f"{repository}/{package_id}" + info_url = f'{package_url}/info.json' + logger.info(f"Reading remote info of {package_id} from repository {repository}") + with urllib.request.urlopen(info_url, timeout=2) as data: + remote_info = json.load(data) + logger.debug(f"{package_id} remote info: {remote_info}") + + # TODO Remove 'or' condition -> "or remote_info["version"]==0" + if remote_info["version"] > latest_version_repository[0] or remote_info["version"]==0: + latest_version_repository = (remote_info["version"], package_url) + except Exception as e: + logger.error(f"Error reading remote info of {package_id} from repository {repository}: {e}") + + if latest_version_repository[1]: + package_zip = f"{latest_version_repository[1]}/{package_id}.zip" + package_local_file = os.path.join(self.libs_path, f"{package_id}.zip") + logger.debug(f"Downloading package from {package_zip} to local file {package_local_file}") + self.download_package(package_zip, package_local_file) + self.pip_install_requirements(package_path) + + + def download_package(self, url, path): + urllib.request.urlretrieve(url, path) + logger.info(f"Extracting all from {path}") + with zipfile.ZipFile(path, 'r') as zip_ref: + zip_ref.extractall(Path(path).parent.absolute()) + + def pip_install_requirements(self, package_path): + path = os.path.join(package_path, "requirements.txt") + if(os.path.exists(path)): + logger.info(f"Pip installing requirements in {path}") + subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", path]) + else: + logger.info(f"{package_path} no requirements.txt") + + def check_module_requirements(self, package_path): + pass \ No newline at end of file diff --git a/app/BKP/utils.py b/app/BKP/utils.py new file mode 100644 index 0000000..f92f630 --- /dev/null +++ b/app/BKP/utils.py @@ -0,0 +1,256 @@ +import os, sys +import uuid +import pathlib +import json +import subprocess + +root_dir = os.path.normpath(__file__.split("libs")[0]) +node_id = str(uuid.uuid4()) + +### LOGGER ### +import logging, inspect + +class CustomLoggingFormatter(logging.Formatter): + grey = "\x1b[0;37m" + green = "\x1b[1;32m" + yellow = "\x1b[1;33m" + red = "\x1b[1;31m" + purple = "\x1b[1;35m" + blue = "\x1b[1;34m" + light_blue = "\x1b[1;36m" + bold_red = "\x1b[31;1m" + blink_red = "\x1b[5m\x1b[1;31m" + reset = "\x1b[0m" + prefix = light_blue + '%(asctime)s' + reset + ' |' + colored_level = '%(levelname)-8s' + message = '| %(message)s' + '' + suffix = purple + ' (%(name)s %(filename)s:%(lineno)d)' + reset + + FORMATS = { + logging.DEBUG: prefix + grey + colored_level + reset + message + suffix, + logging.INFO: prefix + blue + colored_level + reset + message + suffix, + logging.WARNING: prefix + yellow + colored_level + reset + message + suffix, + logging.ERROR: prefix + red + colored_level + reset + message + suffix, + logging.CRITICAL: prefix + bold_red + colored_level + reset + message + suffix + } + + file_format = '%(asctime)s | %(levelname)-8s | %(message)s | %(name)s (%(filename)s:%(lineno)d)' + + def format(self, record): + log_fmt = self.FORMATS.get(record.levelno) + formatter = logging.Formatter(log_fmt) + return formatter.format(record) + +# class FileFilter(logging.Filter): +# def filter(self, record): +# file_path = os.path.normpath(inspect.stack()[1].filename) +# head = os.path.split(file_path)[0] +# tail = os.path.split(file_path)[1] +# if head == os.path.normpath(root_dir): +# name = "app" +# else: +# after_lib = head.replace(os.path.join(root_dir, "libs"), "") +# name = after_lib.split(os.sep)[1] +# record.name = name +# print(name) +# return True + + +def add_logging_console_handler(logger:logging.Logger, logging_level=logging.DEBUG): + sh = logging.StreamHandler() + sh.setLevel(logging_level) + sh.setFormatter(CustomLoggingFormatter()) + logger.addHandler(sh) + +def add_logging_file_handler(logger:logging.Logger, directory, file_name, logging_level=logging.DEBUG): + file_path = os.path.join(root_dir, "logs", directory, f"{file_name}.log") + pathlib.Path(file_path).parent.mkdir(parents=True, exist_ok=True) + fh = logging.FileHandler(file_path) + fh.setLevel(logging_level) + fh.setFormatter(logging.Formatter(CustomLoggingFormatter().file_format)) + logger.addHandler(fh) + +def config_root_logger(): + logger = get_logger(name="root") + # TODO Get lib that is calling logging + # logger.addFilter(FileFilter()) + +def get_logger(name=""): + file_path = os.path.normpath(inspect.stack()[1].filename) + head = os.path.split(file_path)[0] + tail = os.path.split(file_path)[1] + if head == os.path.normpath(root_dir): + lib = "root" + else: + after_lib = head.replace(os.path.join(root_dir, "libs"), "") + lib = after_lib.split(os.sep)[1] + + if name == "root": + logger_name = name + elif name: + logger_name = f"{lib}.{name}" + else: + logger_name = lib + + file_name = name if name else lib + logger = logging.getLogger(logger_name) + logger.propagate = False + if not logger.hasHandlers(): + logger.setLevel(logging.DEBUG) + add_logging_console_handler(logger=logger, logging_level=logging.DEBUG) + add_logging_file_handler(logger=logger, directory=lib, file_name=file_name, logging_level=logging.DEBUG) + + return logger + +logger = get_logger() +logger.debug(f"Root dir: {root_dir}") + +### VENV ### +import venv + +def create_venv(): + venv_dir = os.path.join(root_dir, ".venv") + if not os.path.exists(venv_dir): + logger.debug(f"Creating python venv: {venv_dir}") + venv.create(venv_dir, with_pip=True) + +### DOWNLOADS ### +import urllib.request + +def download_file(url, path): + head = os.path.split(path)[0] + if not os.path.exists(head): + logger.debug(f"Creating repository {head}") + pathlib.Path(head).mkdir(parents=True, exist_ok=True) + + logger.debug(f"Downloading from URL: {url} to {path}") + urllib.request.urlretrieve(url, path) + +### KARGS ### +def read_kargs(): + kargs = {} + params = sys.argv + for param in params: + if "=" in param: + split = param.split("=") + key = split[0] + value = split[1] + + if value == "true" or value == "True": + value = True + elif value == "false" or value == "False": + value = False + + kargs[key] = value + kargs["rootDir"] = root_dir + return kargs + +def kargs_to_array(kargs={}): + if not kargs: + kargs = read_kargs() + array = [] + for key, value in kargs.items(): + array.append(f"{key}={value}") + return array + +### HTTP UTILS ### +def is_http_running(url): + try: + u:urllib.request.URLopener = urllib.request.urlopen(url) + u.close() + return True + except: + return False + +### UTILS ### + +class Utils: + def __init__(self): + self.kargs = read_kargs() + self.configs = Config() + self.flags = {} + + # def get_env(): + # return os.environ.get('NOSYS_ENV', "PROD") + + # def download_file(self, package, file, destination, repository=None): + # if not repository: + # repository = self.default_repository + + # url = f"{repository}/{package}/{file}" + # logger.debug(f"Downloading {file} from URL: {url}") + # urllib.request.urlretrieve(url, destination) + + # def download_main_file(self, ignoreIfExists=True): + # main_path = os.path.join(self.root_dir, "libs","app", "main.py") + # if (not os.path.exists(main_path)) or (os.path.exists(main_path) and not ignoreIfExists): + # self.download_file(package="app", file="main.py", destination=main_path) + + def is_terminal_visible(self): + try: + return self.configs.data["main"]["terminalWindow"] + except Exception as e: + return True + + # def _create_log_dir(self): + # logs_path = os.path.join(self.root_dir, "logs") + # if not os.path.exists(logs_path): + # logger.debug(f"Creating logs directory: {logs_path}") + # pathlib.Path(logs_path).mkdir(parents=True, exist_ok=True) + + def new_python_process(self, file_path, args): + python_executable = "python" if self.is_terminal_visible() else "pythonw" + args = [f"{root_dir}/.venv/scripts/{python_executable}", file_path] + args + logger.debug(f"Starting a new process: {args}") + process = subprocess.Popen(args, cwd=root_dir, creationflags=subprocess.CREATE_NEW_CONSOLE) + logger.debug(f"Process PID {process.pid} - {args}") + return process.pid + + def restart_app(self): + args = [sys.executable, "start.py"] + kargs_to_array(self.kargs) + subprocess.Popen(args, cwd=root_dir, creationflags=subprocess.CREATE_NEW_CONSOLE) + logger.info(f"Restarting app: {args}") + self.exit_app() + + def exit_app(self, exit_code=1): + os._exit(exit_code) + # sys.exit(exit_code) + +class Config: + def __init__(self): + self.data = None + self.packages:dict[str, Package] = {} + + self.read_config() + + def read_config(self): + config_path = os.path.join(root_dir, "libs", "app", "config.json") + if(os.path.exists(config_path)): + with open(config_path) as f: + data = json.load(f) + self.data = data + self.read_packages() + else: + raise Exception(f"Config file {config_path} not exist") + + def read_packages(self): + for package in self.data["packages"]: + try: + with open(os.path.join(root_dir,'libs',package["id"],'info.json')) as f: + self.packages[package["id"]] = Package(config=package, info=json.load(f)) + except Exception as e: + self.packages[package["id"]] = Package(config=package, info={}) + logger.error(e) + +class Package: + def __init__(self, config={}, info={}): + self.config = config + self.info = info + self.modules:dict[str, any] = {} + + self.read_modules() + + def read_modules(self): + if "modules" in self.info: + for module in self.info["modules"]: + self.modules[module["id"]] = module \ No newline at end of file diff --git a/app/common/args.py b/app/common/args.py new file mode 100644 index 0000000..d07172e --- /dev/null +++ b/app/common/args.py @@ -0,0 +1,51 @@ +import sys +from typing import Dict, List, Any + +def read_kargs(argv: List[str] = None) -> Dict[str, Any]: + """ + Parse command-line arguments in the format key=value. + + Example: + python start.py updateApp=True port=8080 + + Returns: + dict: { + "updateApp": True, + "port": "8080" + } + """ + if argv is None: + argv = sys.argv + + kargs: Dict[str, Any] = {} + for param in argv: + if "=" in param: + key, value = param.split("=", 1) + + # Normalize booleans + if value.lower() == "true": + value = True + elif value.lower() == "false": + value = False + + kargs[key] = value + + return kargs + + +def kargs_to_array(kargs: Dict[str, Any] = None) -> List[str]: + """ + Convert a dictionary of arguments back to an array of key=value. + + Example: + {"updateApp": True, "port": 8080} + Returns: + ["updateApp=True", "port=8080"] + """ + if not kargs: + kargs = read_kargs() + + array: List[str] = [] + for key, value in kargs.items(): + array.append(f"{key}={value}") + return array \ No newline at end of file diff --git a/app/common/config.py b/app/common/config.py new file mode 100644 index 0000000..3924c88 --- /dev/null +++ b/app/common/config.py @@ -0,0 +1,56 @@ +import json +import os +from pathlib import Path +from typing import Dict, Any +from collections import OrderedDict + +from .paths import LIBS_DIR + +class Config: + def __init__(self): + self.libs: Dict[str, Dict[str, Any]] = {} + self.load_libs_config() + + def load_libs_config(self): + for lib_path in LIBS_DIR.iterdir(): + if lib_path.is_dir(): + info_path = lib_path / "info.json" + config_path = lib_path / "config.json" + lib_id = lib_path.name + + info_data = {} + config_data = {} + + if info_path.exists(): + with open(info_path) as f: + info_data = json.load(f, object_pairs_hook=OrderedDict) + + if config_path.exists(): + with open(config_path) as f: + config_data = json.load(f, object_pairs_hook=OrderedDict) + + config_data["info"] = info_data + self.libs[lib_id] = config_data + + def get(self, lib: str, *keys, default=None): + """ + Retrieves values from the config. + + Example: + config.get("app", "libs", "app", "update", "version", default="1.0") + + Parameters: + - lib: the name of the library + - *keys: a sequence of keys to navigate through nested dictionaries + - default: the value to return if any key in the chain does not exist + + Returns: + - The value found at the nested key path, or `default` if a key is missing. + """ + value = self.libs.get(lib, default) + for key in keys: + if isinstance(value, dict) and key in value: + value = value[key] + else: + return default + return value diff --git a/app/common/logging.py b/app/common/logging.py new file mode 100644 index 0000000..10e75b4 --- /dev/null +++ b/app/common/logging.py @@ -0,0 +1,173 @@ +import os +import logging +import inspect +from collections import deque +from typing import Deque +from io import StringIO +from pathlib import Path +from .paths import ROOT_DIR, LOGS_DIR + + +class CustomLoggingFormatter(logging.Formatter): + """ + Custom logging formatter with ANSI colors for console output + and standard format for file output. + """ + grey = "\x1b[0;37m" + green = "\x1b[1;32m" + yellow = "\x1b[1;33m" + red = "\x1b[1;31m" + purple = "\x1b[1;35m" + blue = "\x1b[1;34m" + light_blue = "\x1b[1;36m" + bold_red = "\x1b[31;1m" + reset = "\x1b[0m" + + prefix = light_blue + '%(asctime)s' + reset + ' |' + colored_level = '%(levelname)-8s' + message = '| %(message)s' + suffix = purple + ' (%(name)s %(filename)s:%(lineno)d)' + reset + + FORMATS = { + logging.DEBUG: prefix + grey + colored_level + reset + message + suffix, + logging.INFO: prefix + blue + colored_level + reset + message + suffix, + logging.WARNING: prefix + yellow + colored_level + reset + message + suffix, + logging.ERROR: prefix + red + colored_level + reset + message + suffix, + logging.CRITICAL: prefix + bold_red + colored_level + reset + message + suffix, + } + + file_format = '%(asctime)s | %(levelname)-8s | %(message)s | %(name)s (%(filename)s:%(lineno)d)' + + def format(self, record): + log_fmt = self.FORMATS.get(record.levelno, self.file_format) + formatter = logging.Formatter(log_fmt) + return formatter.format(record) + +def add_logging_console_handler(logger: logging.Logger, logging_level=logging.DEBUG): + """ + Add console handler with colored formatter to a logger. + """ + sh = logging.StreamHandler() + sh.setLevel(logging_level) + sh.setFormatter(CustomLoggingFormatter()) + logger.addHandler(sh) + +def add_logging_file_handler(logger: logging.Logger, directory: str, file_name: str, logging_level=logging.DEBUG): + """ + Add file handler with plain formatter to a logger. + """ + file_path = LOGS_DIR / directory / f"{file_name}.log" + file_path.parent.mkdir(parents=True, exist_ok=True) + fh = logging.FileHandler(file_path, encoding="utf-8") + fh.setLevel(logging_level) + fh.setFormatter(logging.Formatter(CustomLoggingFormatter().file_format)) + logger.addHandler(fh) + +class BufferHandler(logging.Handler): + """ + Logging handler that stores log records in a limited deque. + Allows multiple listeners to be notified when a new log line is added. + """ + def __init__(self, maxlen: int = 200): + super().__init__() + self.buffer: Deque[str] = deque(maxlen=maxlen) + self.formatter = CustomLoggingFormatter() + self._listeners: list[callable] = [] + + def emit(self, record: logging.LogRecord): + for listener in self._listeners: + try: + listener(record) + except Exception as e: + print(f"BufferHandler listener error: {e}") + + def get_logs(self): + return list(self.buffer) + + def clear(self): + self.buffer.clear() + + def register_listener(self, listener: callable): + """ + Register a listener callback that will be called with + each new log line. + """ + if listener not in self._listeners: + self._listeners.append(listener) + + def unregister_listener(self, listener: callable): + """Remove a previously registered listener.""" + if listener in self._listeners: + self._listeners.remove(listener) + +_LOGGER_BUFFERS: dict[str, BufferHandler] = {} + +def add_logging_buffer_handler(logger: logging.Logger, logging_level=logging.DEBUG, maxlen: int = 300) -> BufferHandler: + """ + Add buffer handler with plain formatter to a logger. + Returns the buffer so it can be accessed later. + """ + bh = BufferHandler(maxlen=maxlen) + logger.addHandler(bh) + _LOGGER_BUFFERS[logger.name] = bh + +def get_logger(name: str = "", buffer: bool = False) -> logging.Logger: + """ + Returns a logger instance with console, file, and optional buffer handlers. + Logger name is automatically derived from caller module path. + """ + file_path = os.path.normpath(inspect.stack()[1].filename) + head = os.path.split(file_path)[0] + + # Determine library name + if head == os.path.normpath(ROOT_DIR): + lib = "root" + else: + after_lib = head.replace(os.path.join(ROOT_DIR, "libs"), "") + lib = after_lib.split(os.sep)[1] if len(after_lib.split(os.sep)) > 1 else "unknown" + + # Build logger name + if name == "root": + logger_name = name + elif name: + logger_name = f"{lib}.{name}" + else: + logger_name = lib + + file_name = name if name else lib + + logger = logging.getLogger(logger_name) + logger.propagate = False + + if not logger.hasHandlers(): + logger.setLevel(logging.DEBUG) + add_logging_console_handler(logger=logger, logging_level=logging.DEBUG) + add_logging_file_handler(logger=logger, directory=lib, file_name=file_name, logging_level=logging.DEBUG) + if buffer and logger_name not in _LOGGER_BUFFERS: + add_logging_buffer_handler(logger, logging_level=logging.DEBUG) + + return logger + +def get_logger_buffer(logger_name: str): + """ + Returns the buffer of a previously configured logger. + Raises KeyError if the logger does not have a buffer handler configured or does not exist. + """ + if logger_name in _LOGGER_BUFFERS: + return _LOGGER_BUFFERS[logger_name] + raise KeyError(f"Logger '{logger_name}' does not have a buffer handler configured or does not exist.") + +def list_loggers() -> list[str]: + """ + Returns a list of all currently registered logger names. + """ + return sorted([ + name for name, obj in logging.Logger.manager.loggerDict.items() + if isinstance(obj, logging.Logger) + ]) + +def config_root_logger() -> logging.Logger: + """ + Configure and return the root logger. + """ + return get_logger(name="root") diff --git a/app/common/network_utils.py b/app/common/network_utils.py new file mode 100644 index 0000000..59de859 --- /dev/null +++ b/app/common/network_utils.py @@ -0,0 +1,25 @@ +import requests +from requests.exceptions import RequestException, Timeout + + +def check_url(url: str, timeout: int = 10, retries: int = 3, verify_ssl: bool = False) -> bool: + """ + Check if a given URL is reachable. + + Args: + url (str): The URL to check. + timeout (int): Timeout for each request in seconds. + retries (int): Number of retry attempts before failing. + verify_ssl (bool): Verify SSL. + + Returns: + bool: True if the URL is reachable, False otherwise. + """ + for attempt in range(retries): + try: + response = requests.head(url, timeout=timeout, allow_redirects=True, verify=verify_ssl) + if 200 <= response.status_code < 400: + return True + except (RequestException, Timeout): + continue + return False \ No newline at end of file diff --git a/app/common/paths.py b/app/common/paths.py new file mode 100644 index 0000000..f4d601c --- /dev/null +++ b/app/common/paths.py @@ -0,0 +1,8 @@ +from pathlib import Path + +# Root directory of the project +ROOT_DIR = Path(__file__).resolve().parents[3] + +LOGS_DIR = ROOT_DIR / "logs" +LIBS_DIR = ROOT_DIR / "libs" +FILES_DIR = ROOT_DIR / "files" diff --git a/app/common/process.py b/app/common/process.py new file mode 100644 index 0000000..05a7b70 --- /dev/null +++ b/app/common/process.py @@ -0,0 +1,97 @@ +import subprocess +import sys +import os +import platform +import shutil +from typing import List, Optional +from .paths import ROOT_DIR # centralized project paths + + +def _find_terminal_emulator() -> Optional[str]: + """ + Try to find an available terminal emulator on Linux. + Returns the command name if found, otherwise None. + """ + candidates = [ + "x-terminal-emulator", + "gnome-terminal", + "konsole", + "xfce4-terminal", + "lxterminal", + "mate-terminal", + "tilix", + "terminator", + "xterm" + ] + for term in candidates: + if shutil.which(term): + return term + return None + + +def new_python_process( + script_path: str, + python_exec: str = sys.executable, + args: Optional[List[str]] = None, + cwd: str = str(ROOT_DIR), + wait: bool = False, + new_console: bool = False +) -> int: + """ + Start a new Python process running the given script. + + Args: + script_path (str): Path to the Python script to execute. + python_exec (str): Path to the Python executable. + args (List[str], optional): Arguments to pass to the script. Defaults to []. + cwd (str): Working directory for the process. Defaults to project root. + wait (bool): If True, waits for process completion. Defaults to False. + new_console (bool): If True, tries to open process in a new console window. + + Returns: + int: PID of the created process. + """ + if not os.path.isabs(python_exec): + python_exec = os.path.normpath(os.path.join(cwd, python_exec)) + + if not os.path.isabs(script_path): + script_path = os.path.normpath(os.path.join(cwd, script_path)) + + if args is None: + args = [] + + command = [python_exec, script_path] + args + + creation_flags = 0 + shell = False + + system = platform.system().lower() + + if new_console: + if system == "windows": + creation_flags = subprocess.CREATE_NEW_CONSOLE + elif system == "linux": + term = _find_terminal_emulator() + if term: + command = [term, "-e"] + command + else: + # fallback: run in same terminal + pass + elif system == "darwin": # MacOS + osa_cmd = f'tell app "Terminal" to do script "{python_exec} {script_path} {" ".join(args)}"' + process = subprocess.Popen(["osascript", "-e", osa_cmd], cwd=cwd) + if wait: + process.wait() + return process.pid + + process = subprocess.Popen( + command, + cwd=cwd, + creationflags=creation_flags, + shell=shell + ) + + if wait: + process.wait() + + return process.pid diff --git a/app/common/store.py b/app/common/store.py new file mode 100644 index 0000000..92a5c5b --- /dev/null +++ b/app/common/store.py @@ -0,0 +1,75 @@ +import json +import os +from pathlib import Path +from typing import List, Dict, Any, Optional + +from libs.app.common.paths import FILES_DIR + +class DataStore: + def __init__(self, path: str = "data/data.json", default_data:Optional[Dict[str, List[Any]]] = None, autosave:bool = True): + if default_data is None: + default_data = {"data": []} + if not os.path.isabs(path): + path = os.path.normpath(os.path.join(FILES_DIR, path)) + self.file_path = Path(path) + self.default_data = default_data + self.data = default_data.copy() + self.autosave = autosave + + self.load() + + def load(self): + if self.file_path.exists(): + with open(self.file_path, "r", encoding="utf-8") as f: + self.data = json.load(f) + else: + os.makedirs(self.file_path.parent, exist_ok=True) + self._save() + + def save(self): + with open(self.file_path, "w", encoding="utf-8") as f: + json.dump(self.data, f, indent=2) + + def _save(self): + if not self.autosave: + return + self.save() + + def clear(self): + self.data = self.default_data.copy() + self._save() + + + def add_item(self, parent:str, data: Dict[str, Any], unique: bool = False, id_field:Optional[str]=None, id: Optional[str]=None): + if unique and id and id_field: + if any(item.get(id_field) == id for item in self.data.get(parent, [])): + raise ValueError(f"Item already exists in {parent}. {id_field}:{id}") + + self.data.setdefault(parent, []).append(data) + self._save() + + def get_item(self, parent:str, id_field:str, id:str) -> Optional[Dict[str, Any]]: + for item in self.data.get(parent, []): + if item.get(id_field) == id: + return item + return None + + def update_item(self, parent: str, id_field: str, id: str, updates: Dict[str, Any]) -> bool: + for item in self.data.get(parent, []): + if item.get(id_field) == id: + item.update(updates) + self._save() + return True + return False + + def remove_item(self, parent:str, id_field:str, id:str) -> bool: + items = self.data.get(parent, []) + new_items = [item for item in items if item.get(id_field) != id] + if len(new_items) != len(items): + self.data[parent] = new_items + self._save() + return True + return False + + def list_items(self, parent:str) -> List[Dict[str, Any]]: + return self.data.get(parent, []) \ No newline at end of file diff --git a/app/config.json b/app/config.json new file mode 100644 index 0000000..4bb5889 --- /dev/null +++ b/app/config.json @@ -0,0 +1,44 @@ +{ + "terminalWindow": true, + + "repositories":["https://n0sys.duckdns.org/downloads/libs"], + + "libs":[ + { + "id":"app", + "update": { + "checkUpdates": true, + "version": 0, + "repositories": ["http://custom.repo/rendezvous"] + }, + "enabled": true + }, + { + "id":"fspn" + }, + { + "id":"api" + }, + { + "id":"noSys" + }, + { + "id":"fileTransfer" + }, + { + "id":"vueNoSys" + }, + { + "id":"lockbox" + }, + { + "id":"rendezvous" + }, + { + "id":"p2post" + }, + { + "id":"p2private" + } + ] +} \ No newline at end of file diff --git a/app/info.json b/app/info.json new file mode 100644 index 0000000..21c5ad2 --- /dev/null +++ b/app/info.json @@ -0,0 +1,5 @@ +{ + "id": "app", + "version": 0.111, + "modules": [] +} \ No newline at end of file diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..f57d9d4 --- /dev/null +++ b/app/main.py @@ -0,0 +1,117 @@ +import os, sys +import time +import logging +import traceback +import webbrowser +import webview +import threading + +from common.paths import ROOT_DIR +sys.path.insert(0, str(ROOT_DIR)) + +from libs.app.common.logging import get_logger, get_logger_buffer, list_loggers +from libs.app.common.args import read_kargs, kargs_to_array +from libs.app.common.config import Config +from libs.app.common.network_utils import check_url +from libs.app.updater import Updater +from libs.app.ui.ui_server import UIServer + +logger = get_logger(buffer=True) +logging.getLogger("urllib3.connectionpool").setLevel(logging.WARNING) + + +class App: + def __init__(self): + self.config = Config() + + self.nosys_api_host = self.config.get("api", "server", "host") + self.nosys_api_port = self.config.get("api", "server", "port") + + self.ui_server = UIServer() + self.ui_server.start() + + self.updater = Updater(self.config) + self.updater.register_listener(self.on_updater_event) + + get_logger_buffer("app").register_listener(self.on_log) + get_logger_buffer("app.updater").register_listener(self.on_log) + + def start_frontend(self): + url = "http://127.0.0.1:5000/" + logger.debug("Opening browser...") + webbrowser.open(url) + + # logger.debug("Starting webview ...") + # webview.create_window( + # "NoSys", + # url, + # width=800, + # height=800, + # resizable=True, + # confirm_close=False, + # text_select=True, + # frameless=False, + # background_color="#000000", + # ) + + # webview.start(debug=True) + + def update_libraries(self): + """Update libs and restart if app itself was updated.""" + updated = self.updater.update_libs() + if updated.get("app") == "Success updated": + logger.warning("App updated, restarting...") + self.ui_server.emit_event("restarting") + self.ui_server.stop() + os.execv(sys.executable, [sys.executable] + sys.argv) + + def start_nosys(self): + url = f"https://{self.nosys_api_host}:{self.nosys_api_port}/api/api/health" + + logger.debug(f"Checking NoSys Server {url} ...") + if check_url(url, 3, 1, verify_ssl=True): + logger.debug("NoSys already running") + self.ui_server.emit_event("redirect", {"url": f"https://{self.nosys_api_host}:{self.nosys_api_port}"}) + return + + logger.debug("Starting NoSys") + self.ui_server.emit_event("nosys_starting") + from libs.noSys.noSysCore import NoSysCore + from libs.noSys.events import Events + + self.nosys_core = NoSysCore() + get_logger_buffer("noSys").register_listener(self.on_log) + get_logger_buffer("noSys").register_listener(self.on_log) + get_logger_buffer("noSys.moduleManager").register_listener(self.on_log) + self.nosys_core.subscribe_event(Events.READY, self.on_nosys_ready) + self.nosys_core.start() + + def start(self): + self.start_frontend() + self.update_libraries() + self.config.load_libs_config() + self.start_nosys() + + def on_updater_event(self, event): + if event.name == "status_lib": + self.ui_server.emit_event(event.name, {"lib":event.lib, "status": event.status}) + + def on_log(self, record): + self.ui_server.emit_event("log", {"levelname":record.levelname, "message":record.message}) + + def on_nosys_ready(self, event): + logger.debug(f"Stoping startup frontend server and redirecting to nosys server {self.nosys_api_host}:{self.nosys_api_port} ...") + self.ui_server.emit_event("redirect", {"url": f"https://{self.nosys_api_host}:{self.nosys_api_port}"}) + self.ui_server.stop() + +def main(): + try: + app = App() + app.start() + input("Done\nPress any key to close\n") + except Exception: + logger.exception("Error") + input("Error\nPress any key to close\n") + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 0000000..7067889 --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,4 @@ +requests +Flask +flask-socketio +pywebview \ No newline at end of file diff --git a/app/start.py b/app/start.py new file mode 100644 index 0000000..1e3dd20 --- /dev/null +++ b/app/start.py @@ -0,0 +1,142 @@ +import os +import sys +import logging +from logging.handlers import RotatingFileHandler +import shutil +import pathlib +import urllib.request +import subprocess +import zipfile +import venv +from pathlib import Path + +# ============================== +# Configuration constants +# ============================== +ROOT_DIR = Path(__file__).parent.resolve() +LIBS_DIR = ROOT_DIR / "libs" +LOGS_DIR = ROOT_DIR / "logs" +APP_MAIN = LIBS_DIR / "app" / "main.py" +APP_ZIP = LIBS_DIR / "app.zip" +DEFAULT_REPOSITORY = "https://n0sys.duckdns.org/downloads/libs" +ARGS_LIST = ["updateApp=False", "updateLibs=True", "repack=True"] + +# ============================== +# Logger setup +# ============================== +def setup_logger() -> logging.Logger: + """Configure application logger with console and rotating file handlers.""" + logger = logging.getLogger("start") + logger.setLevel(logging.DEBUG) + + LOGS_DIR.mkdir(parents=True, exist_ok=True) + + # Log format + formatter = logging.Formatter( + fmt="%(asctime)s | %(levelname)-8s | %(message)s", + datefmt="%Y-%m-%d %H:%M:%S" + ) + + # Console handler + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.INFO) + console_handler.setFormatter(formatter) + logger.addHandler(console_handler) + + # File handler + file_handler = RotatingFileHandler(LOGS_DIR / "start.log", maxBytes=5_000_000, backupCount=3, encoding="utf-8") + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + + return logger + +logger = setup_logger() + +# ============================== +# Functions +# ============================== +def start_app(): + """Entry point for the launcher""" + logger.info("--------- START ---------") + logger.debug(f"Root path: {ROOT_DIR}") + + LIBS_DIR.mkdir(parents=True, exist_ok=True) + + # TODO: remove in production + # update_libs() + + create_venv() + ensure_app() + + # Import after app is available + from libs.app.common.process import new_python_process + from libs.app.common.args import read_kargs, kargs_to_array + + args = kargs_to_array() + pid = new_python_process(APP_MAIN, str(get_venv_python()), args=args, wait=True, new_console=True) + logger.info(f"Main process running. PID {pid} - Args: {args}") + logger.info("--------- END ---------") + + +def get_venv_python(): + """Return the path to the venv's Python executable in a cross-platform way""" + if os.name == "nt": + return ROOT_DIR / ".venv" / "Scripts" / "python.exe" + else: + return ROOT_DIR / ".venv" / "bin" / "python" + +def update_libs(): + """Development only: repack local libs""" + if ("updateApp=True" in ARGS_LIST or "updateLibs=True" in ARGS_LIST) and "repack=True" in ARGS_LIST: + update_version_script = r"C:\Workspace\utils\updateLibsVersion.py" + try: + subprocess.run( + [str(get_venv_python()), update_version_script], + cwd=ROOT_DIR, + creationflags=subprocess.CREATE_NEW_CONSOLE, + check=True, + ) + except subprocess.CalledProcessError as e: + logger.error(f"Error running update libs: {e}") + +def ensure_app(): + """Download, extract and install requirements of the application if not already present""" + if APP_MAIN.exists(): + logger.debug("App already present, skipping download.") + return + + url = f"{DEFAULT_REPOSITORY}/app/app.zip" + logger.info(f"Downloading app from {url} ...") + try: + urllib.request.urlretrieve(url, APP_ZIP) + except Exception as e: + logger.exception(f"Failed to download app") + raise + + logger.info(f"Extracting {APP_ZIP}...") + with zipfile.ZipFile(APP_ZIP, 'r') as zip_ref: + zip_ref.extractall(LIBS_DIR) + + logger.info(f"Installing app requirements ...") + python_exec = str(get_venv_python()) + requirements_path = os.path.join(ROOT_DIR, "libs/app/requirements.txt") + subprocess.check_call([python_exec, "-m", "pip", "install", "-r", requirements_path]) + + +def create_venv(): + """Ensure a local virtual environment exists.""" + venv_dir = os.path.join(ROOT_DIR, ".venv") + if not os.path.exists(venv_dir): + logger.debug(f"Creating python venv: {venv_dir}") + venv.create(venv_dir, with_pip=True) + +# ============================== +# Main +# ============================== +if __name__ == "__main__": + try: + start_app() + except Exception: + logger.exception("Error starting application") + sys.exit(1) diff --git a/app/ui/static/main.js b/app/ui/static/main.js new file mode 100644 index 0000000..4f487a1 --- /dev/null +++ b/app/ui/static/main.js @@ -0,0 +1,65 @@ +const socket = io(); +// const socket = io("http://127.0.0.1:5000"); + +socket.on("connect", () => { + console.log("Connected..."); + socket.emit("frontend_ready"); +}); + +const logContainer = document.getElementById("log-container"); + +function appendLog(data) { + const line = document.createElement("div"); + line.textContent = data.levelname + " : " + data.message; + logContainer.appendChild(line); + logContainer.scrollTop = logContainer.scrollHeight; +} + +const statusTree = document.getElementById("updater-tree"); +let statusTreeData = {}; + +function renderStatusTable() { + let html = ` + + + + + + `; + for (const [module, status] of Object.entries(statusTreeData)) { + html += ` + + + + + `; + } + html += `
MóduloStatus
${module}${status}
`; + statusTree.innerHTML = html; +} + +socket.on("status_lib", (data) => { + console.log("lib status", data); + + statusTreeData[data.lib] = data.status; + + renderStatusTable(); +}); + +socket.on("redirect", (data) => { + console.log("redirect", data); + location.replace(data.url); +}); + +socket.on("restarting", (data) => { + console.log("restarting", data); + location.reload() +}); + +socket.on("log", (data) => { + appendLog(data); +}); + +socket.on("error", (data) => { + appendLog(data); +}); diff --git a/app/ui/templates/index.html b/app/ui/templates/index.html new file mode 100644 index 0000000..5c37d7d --- /dev/null +++ b/app/ui/templates/index.html @@ -0,0 +1,22 @@ + + + + NoSys Startup + + + +

NoSys Startup Status

+ +
+ + + + + + + diff --git a/app/ui/ui_server.py b/app/ui/ui_server.py new file mode 100644 index 0000000..704799c --- /dev/null +++ b/app/ui/ui_server.py @@ -0,0 +1,76 @@ +from libs.app.common.logging import get_logger +from flask import Flask, render_template, request +from flask_socketio import SocketIO +import multiprocessing +import threading +import time + +logger = get_logger() + +class UIServer: + def __init__(self, host="127.0.0.1", port=5000): + self.host = host + self.port = port + self.process = None + self.queue = multiprocessing.Queue() + self.client_ready = multiprocessing.Value("b", False) + + @staticmethod + def run_server(host, port, queue, client_ready): + app = Flask(__name__) + socketio = SocketIO(app, cors_allowed_origins="*") + + @app.route("/") + def index(): + return render_template("index.html") + + @socketio.on("frontend_ready") + def frontend_ready(): + client_ready.value = True + + def queue_listener(): + while True: + event, data = queue.get() + while not client_ready.value: + time.sleep(1) + + socketio.emit(event, data) + + threading.Thread(target=queue_listener, daemon=True).start() + + socketio.run(app, host=host, port=port, debug=False, use_reloader=False) + + def start(self): + if self.process and self.process.is_alive(): + logger.debug("Server is already running") + return + self.process = multiprocessing.Process( + target=UIServer.run_server, + args=(self.host, self.port, self.queue, self.client_ready), + ) + self.process.start() + logger.debug(f"Server started on http://{self.host}:{self.port}") + + def stop(self, wait_queue=True, time_limit=5): + while wait_queue and not self.queue.empty(): + logger.debug(f"Waiting server. {self.queue.qsize()} items in queue") + time.sleep(1) + time_limit-=1 + if time_limit <= 0: + break + + if self.process and self.process.is_alive(): + self.process.terminate() + self.process.join() + logger.debug("Server stopped") + self.process = None + + def restart(self): + logger.debug("Restarting server...") + self.stop() + self.start() + + def emit_event(self, event, data={}): + self.queue.put((event, data)) + + diff --git a/app/updater.py b/app/updater.py new file mode 100644 index 0000000..2c150c5 --- /dev/null +++ b/app/updater.py @@ -0,0 +1,135 @@ +import sys +import subprocess +import urllib.request +import os +import json +import types +import logging +import time +import zipfile +from pathlib import Path +from threading import Thread + +from libs.app.common.config import Config +from libs.app.common.logging import get_logger +from libs.app.common.paths import LIBS_DIR + +logger = get_logger("updater", buffer=True) + +class Updater: + """ + Updater class responsible for managing library updates. + """ + def __init__(self, config:Config): + self.config = config + self.repositories = self.config.get("app", "repositories", default=[]) + self.updated_libs: dict[str, any] = {} + + self._event_listeners: list[callable] = [] + + def update_libs(self): + """Check and update all configured libraries in parallel.""" + logger.debug("Checking libraries updates...") + threads: list[Thread] = [] + + for lib in self.config.get("app", "libs", default=[]): + lib_id = lib.get("id") + self._update_lib_status(lib_id, "Reading configuration") + if lib.get("update", {"checkUpdates":True}).get("checkUpdates", True): + self._update_lib_status(lib_id, "Checking updates") + thread = Thread(target=self.check_lib_update, args=(lib,)) + thread.start() + threads.append(thread) + else: + self._update_lib_status(lib_id, "Checking updates disabled") + + for thread in threads: + thread.join() + + return self.updated_libs + + def check_lib_update(self, lib:dict): + """ + Check for a library update in repositories and install if newer version is available. + """ + lib_id = lib.get("id") + lib_config = self.config.get(lib_id) + lib_repositories = lib.get("update", {"repositories":[]}).get("repositories", []) + lib_path = LIBS_DIR / lib_id + + if not lib_config: + Path(lib_path).mkdir(parents=True, exist_ok=True) + current_version = 0 + else: + current_version = lib_config.get("info").get("version") + + logger.debug(f"Checking updates of lib {lib_id} with current version {current_version}") + + repositories = list(self.repositories) + repositories.extend(lib_repositories) + latest_version = (current_version, None) + + for repo in repositories: + try: + info_url = f"{repo}/{lib_id}/info.json" + logger.debug(f"Reading remote info of {lib_id} from repository {repo}") + + with urllib.request.urlopen(info_url, timeout=2) as response: + remote_info = json.load(response) + logger.debug(f"{lib_id} remote info: {remote_info}") + + remote_version = remote_info.get("version") + if remote_version > latest_version[0]: + latest_version = (remote_version, f"{repo}/{lib_id}") + + except Exception as e: + logger.error(f"Error reading remote info of {lib_id} from {repo}: {e}") + + if latest_version[1]: + lib_url = f"{latest_version[1]}/{lib_id}.zip" + lib_file = LIBS_DIR / f"{lib_id}.zip" + + logger.debug(f"Downloading lib from {lib_url} to {lib_file}") + self._update_lib_status(lib_id, f"Downloading lastest lib version {latest_version[0]}") + self.download_lib(lib_url, lib_file) + + self._update_lib_status(lib_id, f"Installing lib requirements") + self.pip_install_requirements(lib_path) + self._update_lib_status(lib_id, f"Success updated") + else: + self._update_lib_status(lib_id, f"Current version is the latest") + + def download_lib(self, url: str, path: str) -> None: + """Download and extract a lib from repository.""" + urllib.request.urlretrieve(url, path) + logger.info(f"Extracting all from {path}") + with zipfile.ZipFile(path, "r") as zip_ref: + zip_ref.extractall(Path(path).parent.absolute()) + + def pip_install_requirements(self, lib_path: str) -> None: + """Run pip install on requirements.txt if present.""" + requirements_path = os.path.join(lib_path, "requirements.txt") + if os.path.exists(requirements_path): + logger.info(f"Installing requirements from {requirements_path}") + subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", requirements_path]) + else: + logger.info(f"No requirements.txt found for {lib_path}") + + def register_listener(self, callback: callable): + """ + Register a listener callback that will be called with + each new event. + """ + if callback not in self._event_listeners: + self._event_listeners.append(callback) + + def emit_event(self, **kwargs): + event = types.SimpleNamespace() + for k, v in kwargs.items(): + setattr(event, k, v) + for listener in self._event_listeners: + listener(event) + + def _update_lib_status(self, lib, status): + self.updated_libs[lib] = status + self.emit_event(name="status_lib", lib=lib, status=status) \ No newline at end of file diff --git a/app/utilss.py b/app/utilss.py new file mode 100644 index 0000000..9674f04 --- /dev/null +++ b/app/utilss.py @@ -0,0 +1,257 @@ +import os, sys +import uuid +import pathlib +import json +import subprocess + +root_dir = os.path.normpath(__file__.split("libs")[0]) +node_id = str(uuid.uuid4()) + +### LOGGER ### +import logging, inspect + +class CustomLoggingFormatter(logging.Formatter): + grey = "\x1b[0;37m" + green = "\x1b[1;32m" + yellow = "\x1b[1;33m" + red = "\x1b[1;31m" + purple = "\x1b[1;35m" + blue = "\x1b[1;34m" + light_blue = "\x1b[1;36m" + bold_red = "\x1b[31;1m" + blink_red = "\x1b[5m\x1b[1;31m" + reset = "\x1b[0m" + prefix = light_blue + '%(asctime)s' + reset + ' |' + colored_level = '%(levelname)-8s' + message = '| %(message)s' + '' + suffix = purple + ' (%(name)s %(filename)s:%(lineno)d)' + reset + + FORMATS = { + logging.DEBUG: prefix + grey + colored_level + reset + message + suffix, + logging.INFO: prefix + blue + colored_level + reset + message + suffix, + logging.WARNING: prefix + yellow + colored_level + reset + message + suffix, + logging.ERROR: prefix + red + colored_level + reset + message + suffix, + logging.CRITICAL: prefix + bold_red + colored_level + reset + message + suffix + } + + file_format = '%(asctime)s | %(levelname)-8s | %(message)s | %(name)s (%(filename)s:%(lineno)d)' + + def format(self, record): + log_fmt = self.FORMATS.get(record.levelno) + formatter = logging.Formatter(log_fmt) + return formatter.format(record) + +# TODO Dinamicaly get caller lib path +# class FileFilter(logging.Filter): +# def filter(self, record): +# file_path = os.path.normpath(inspect.stack()[1].filename) +# head = os.path.split(file_path)[0] +# tail = os.path.split(file_path)[1] +# if head == os.path.normpath(root_dir): +# name = "app" +# else: +# after_lib = head.replace(os.path.join(root_dir, "libs"), "") +# name = after_lib.split(os.sep)[1] +# record.name = name +# print(name) +# return True + + +def add_logging_console_handler(logger:logging.Logger, logging_level=logging.DEBUG): + sh = logging.StreamHandler() + sh.setLevel(logging_level) + sh.setFormatter(CustomLoggingFormatter()) + logger.addHandler(sh) + +def add_logging_file_handler(logger:logging.Logger, directory, file_name, logging_level=logging.DEBUG): + file_path = os.path.join(root_dir, "logs", directory, f"{file_name}.log") + pathlib.Path(file_path).parent.mkdir(parents=True, exist_ok=True) + fh = logging.FileHandler(file_path) + fh.setLevel(logging_level) + fh.setFormatter(logging.Formatter(CustomLoggingFormatter().file_format)) + logger.addHandler(fh) + +def config_root_logger(): + logger = get_logger(name="root") + # TODO Get lib that is calling logging + # logger.addFilter(FileFilter()) + +def get_logger(name=""): + file_path = os.path.normpath(inspect.stack()[1].filename) + head = os.path.split(file_path)[0] + tail = os.path.split(file_path)[1] + if head == os.path.normpath(root_dir): + lib = "root" + else: + after_lib = head.replace(os.path.join(root_dir, "libs"), "") + lib = after_lib.split(os.sep)[1] + + if name == "root": + logger_name = name + elif name: + logger_name = f"{lib}.{name}" + else: + logger_name = lib + + file_name = name if name else lib + logger = logging.getLogger(logger_name) + logger.propagate = False + if not logger.hasHandlers(): + logger.setLevel(logging.DEBUG) + add_logging_console_handler(logger=logger, logging_level=logging.DEBUG) + add_logging_file_handler(logger=logger, directory=lib, file_name=file_name, logging_level=logging.DEBUG) + + return logger + +logger = get_logger() +logger.debug(f"Root dir: {root_dir}") + +### VENV ### +import venv + +def create_venv(): + venv_dir = os.path.join(root_dir, ".venv") + if not os.path.exists(venv_dir): + logger.debug(f"Creating python venv: {venv_dir}") + venv.create(venv_dir, with_pip=True) + +### DOWNLOADS ### +import urllib.request + +def download_file(url, path): + head = os.path.split(path)[0] + if not os.path.exists(head): + logger.debug(f"Creating repository {head}") + pathlib.Path(head).mkdir(parents=True, exist_ok=True) + + logger.debug(f"Downloading from URL: {url} to {path}") + urllib.request.urlretrieve(url, path) + +### KARGS ### +def read_kargs(): + kargs = {} + params = sys.argv + for param in params: + if "=" in param: + split = param.split("=") + key = split[0] + value = split[1] + + if value == "true" or value == "True": + value = True + elif value == "false" or value == "False": + value = False + + kargs[key] = value + kargs["rootDir"] = root_dir + return kargs + +def kargs_to_array(kargs={}): + if not kargs: + kargs = read_kargs() + array = [] + for key, value in kargs.items(): + array.append(f"{key}={value}") + return array + +### HTTP UTILS ### +def is_http_running(url): + try: + u:urllib.request.URLopener = urllib.request.urlopen(url) + u.close() + return True + except: + return False + +### UTILS ### + +class Utils: + def __init__(self): + self.kargs = read_kargs() + self.configs = Config() + self.flags = {} + + # def get_env(): + # return os.environ.get('NOSYS_ENV', "PROD") + + # def download_file(self, package, file, destination, repository=None): + # if not repository: + # repository = self.default_repository + + # url = f"{repository}/{package}/{file}" + # logger.debug(f"Downloading {file} from URL: {url}") + # urllib.request.urlretrieve(url, destination) + + # def download_main_file(self, ignoreIfExists=True): + # main_path = os.path.join(self.root_dir, "libs","app", "main.py") + # if (not os.path.exists(main_path)) or (os.path.exists(main_path) and not ignoreIfExists): + # self.download_file(package="app", file="main.py", destination=main_path) + + def is_terminal_visible(self): + try: + return self.configs.data["main"]["terminalWindow"] + except Exception as e: + return True + + # def _create_log_dir(self): + # logs_path = os.path.join(self.root_dir, "logs") + # if not os.path.exists(logs_path): + # logger.debug(f"Creating logs directory: {logs_path}") + # pathlib.Path(logs_path).mkdir(parents=True, exist_ok=True) + + def new_python_process(self, file_path, args): + python_executable = "python" if self.is_terminal_visible() else "pythonw" + args = [f"{root_dir}/.venv/scripts/{python_executable}", file_path] + args + logger.debug(f"Starting a new process: {args}") + process = subprocess.Popen(args, cwd=root_dir, creationflags=subprocess.CREATE_NEW_CONSOLE) + logger.debug(f"Process PID {process.pid} - {args}") + return process.pid + + def restart_app(self): + args = [sys.executable, "start.py"] + kargs_to_array(self.kargs) + subprocess.Popen(args, cwd=root_dir, creationflags=subprocess.CREATE_NEW_CONSOLE) + logger.info(f"Restarting app: {args}") + self.exit_app() + + def exit_app(self, exit_code=1): + os._exit(exit_code) + # sys.exit(exit_code) + +class Config: + def __init__(self): + self.data = None + self.packages:dict[str, Package] = {} + + self.read_config() + + def read_config(self): + config_path = os.path.join(root_dir, "libs", "app", "config.json") + if(os.path.exists(config_path)): + with open(config_path) as f: + data = json.load(f) + self.data = data + self.read_packages() + else: + raise Exception(f"Config file {config_path} not exist") + + def read_packages(self): + for package in self.data["packages"]: + try: + with open(os.path.join(root_dir,'libs',package["id"],'info.json')) as f: + self.packages[package["id"]] = Package(config=package, info=json.load(f)) + except Exception as e: + self.packages[package["id"]] = Package(config=package, info={}) + logger.error(e) + +class Package: + def __init__(self, config={}, info={}): + self.config = config + self.info = info + self.modules:dict[str, any] = {} + + self.read_modules() + + def read_modules(self): + if "modules" in self.info: + for module in self.info["modules"]: + self.modules[module["id"]] = module \ No newline at end of file diff --git a/fileTransfer/.mtimes.json b/fileTransfer/.mtimes.json new file mode 100644 index 0000000..88e7765 --- /dev/null +++ b/fileTransfer/.mtimes.json @@ -0,0 +1 @@ +{"file.py": 1757577571.3943994, "fileTransfer.py": 1756117680.0193572} \ No newline at end of file diff --git a/fileTransfer/file.py b/fileTransfer/file.py new file mode 100644 index 0000000..c10cf43 --- /dev/null +++ b/fileTransfer/file.py @@ -0,0 +1,109 @@ +import sys, os +import math +from enum import Enum, auto + +from libs.app.common.logging import get_logger +from libs.fspn.utils.observable import Observable +from libs.fspn.utils.wrapper_util import threaded +from libs.fspn.utils.sha256_util import hash_bytes, hash_file + +logger = get_logger() + +class FileEvents(Enum): + ON_FILE_COMPLETED = auto() + ON_FILE_ERROR = auto() + ON_FILE_UPDATE = auto() + ON_FILE_APPROVED = auto() + +# TODO Add try except and retries +class File(Observable): + def __init__(self, folder, name, size, chunk_size, hash, connection_id, sending, file_transfer, to_module) -> None: + self.id = (hash, connection_id) + self.name = name + self.folder = folder + self.size = size + self.chunk_size = chunk_size + self.parts = [None] * math.ceil(size/chunk_size) + self.hash = hash + self.status = "WAITING" + self.sending = sending + self.connection_id = connection_id + from libs.fileTransfer.fileTransfer import FileTransfer + self.file_transfer:FileTransfer = file_transfer + self.to_module = to_module + + self.output_path = os.path.join(self.folder, self.name + '.download') + self.final_path = self.get_final_path() + + def get_final_path(self): + base_name, ext = os.path.splitext(self.name) + count = 0 + while True: + if count == 0: + filename = f"{base_name}{ext}" + else: + filename = f"{base_name}({count}){ext}" + final_path = os.path.join(self.folder, filename) + if not os.path.exists(final_path): + return final_path + count += 1 + + def approve_transfer(self, approved): + if approved: + self.status = "TRANSFERING" + if self.sending: + self.start_send() + else: + # TODO Check if file exists and same hash + if not os.path.exists(self.output_path): + os.makedirs(self.folder, exist_ok=True) + with open(self.output_path, 'wb') as f: + f.truncate(self.size) + else: + self.status = "CANCELED" + + self.fire_event(FileEvents.ON_FILE_APPROVED.name, approved=approved) + + @threaded + def start_send(self): + logger.warning("Start Sending") + f = open(os.path.join(self.folder, self.name), 'rb') + for part in range(len(self.parts)): + f.seek(part * self.chunk_size) + data = f.read(self.chunk_size) + part_hash = hash_bytes(data) + logger.debug(f"Sending part {part}") + # time.sleep(0.5) + self.file_transfer.send_file_part(self, part, part_hash, data) + f.close() + + @threaded + def write_part(self, data, part, hash): + check_hash = hash_bytes(data) + logger.debug(f"Writing part {part}") + if check_hash == hash: + part_offset = part * self.chunk_size + with open(self.output_path, 'r+b') as f: + f.seek(part_offset) + f.write(data) + + self.parts[part] = True + # TODO Send ack + self.update_status() + else: + # TODO Request part again + logger.error("HASH PART ERROR") + + def update_status(self): + if all(x for x in self.parts): + os.rename(self.output_path, self.final_path) + if hash_file(self.final_path) == self.hash: + logger.info(f'File {self.name} downloaded!') + self.status = "COMPLETED" + self.fire_event(FileEvents.ON_FILE_COMPLETED.name, final_path=self.final_path) + else: + self.status = "CORRUPTED" + logger.warning(f'File {self.name} corrupted') + self.fire_event(FileEvents.ON_FILE_ERROR.name) + else: + self.fire_event(FileEvents.ON_FILE_UPDATE.name) \ No newline at end of file diff --git a/fileTransfer/fileTransfer.py b/fileTransfer/fileTransfer.py new file mode 100644 index 0000000..a5d89e8 --- /dev/null +++ b/fileTransfer/fileTransfer.py @@ -0,0 +1,100 @@ +import sys, os +import math +from enum import Enum, auto +from pathlib import Path +import time + +from libs.noSys.noSysModule import NoSysModule +from libs.app.common.logging import get_logger +from libs.app.common.paths import ROOT_DIR +from libs.fspn.utils.observable import Observable +from libs.fspn.utils.wrapper_util import threaded +from libs.fspn.utils.sha256_util import hash_bytes, hash_file + +from .file import File, FileEvents + +logger = get_logger() + +class FileTransferEvents(Enum): + ON_RECEIVING_ = auto() + +class FileTransfer(NoSysModule): + def __init__(self, nosys_core): + super().__init__(nosys_core) + self.files:dict[tuple[str,str], File] = {} + + def send_file(self, file_path, connection_id, to_module): + path = Path(file_path) + file_folder = path.parent + file_name = path.name + file_size = path.stat().st_size + chunk_size = self.get_dynamic_chunk_size(file_size) + file_hash = hash_file(file_path) + file = File(file_folder, file_name, file_size, chunk_size, file_hash, connection_id, True, self, to_module) + file.subscribe_event(FileEvents.ON_FILE_APPROVED.name, self.on_file_approved) + self.files[file.id] = file + self.send_file_info(connection_id, file_name, file_size, chunk_size, file_hash, to_module) + + def get_dynamic_chunk_size(self, file_size: int) -> int: + min_chunk = 64 * 1024 # 64 KB + max_chunk = 2 * 1024 * 1024 # 2 MB + target_chunks = 500 + ideal_chunk = file_size // target_chunks + return max(min_chunk, min(ideal_chunk, max_chunk)) + + def on_module_message(self, event): + if event.meta: + action = event.meta["action"] + else: + action = event.data['action'] + handler_action = getattr(self, 'on_'+action) + handler_action(event) + + def send_file_info(self, connection_id, name, size, chunk_size, hash, to_module): + body = {"action":"file_info", "name":name, "size":size, "chunk_size":chunk_size, "hash":hash, "module":{"package": to_module[0], "name":to_module[1]}} + self.nosys_core.dispatcher.send_message(body, connection_id, self.id) + + def on_file_info(self, event): + payload = event.data + folder = os.path.join(ROOT_DIR, "files") + to_module = (payload['module']['package'], payload['module']['name']) + file = File(folder, payload["name"], payload["size"], payload["chunk_size"], payload["hash"], event.connection.id, False, self, to_module) + file.subscribe_event(FileEvents.ON_FILE_APPROVED.name, self.on_file_approved) + self.files[file.id] = file + self.fire_event(f"{FileTransferEvents.ON_RECEIVING_.name}{to_module[0]}_{to_module[1]}", file=file) + + def subscribe_module_file_events(self, module:tuple[str, str], callback): + self.subscribe_event(f"{FileTransferEvents.ON_RECEIVING_.name}{module[0]}_{module[1]}", callback) + + def on_file_approved(self, event): + file:File = event.source + approved = event.approved + + if file.sending: + logger.debug(f"Peer file approved {approved}") + else: + self.send_file_transfer_approved(file, approved) + + def send_file_transfer_approved(self, file:File, approved): + body = {"action":"file_transfer_approved", "file_hash":file.hash, "approved":approved} + self.nosys_core.dispatcher.send_message(body, file.connection_id, self.id) + + def on_file_transfer_approved(self, event): + payload = event.data + file_id = (payload["file_hash"], event.connection.id) + file:File = self.files[file_id] + file.approve_transfer(payload["approved"]) + + def send_file_part(self, file:File, part, part_hash, data): + meta = {"action":"file_part", "part":part, "part_hash":part_hash, "file_hash": file.hash} + self.nosys_core.dispatcher.send_binary(data, file.connection_id, self.id, meta) + + def on_file_part(self, event): + meta = event.meta + data = event.data + file_id = (meta["file_hash"], event.connection.id) + file:File = self.files[file_id] + file.write_part(data, meta["part"], meta["part_hash"]) + + # TODO get_file_part, ack_file_part + diff --git a/fileTransfer/fileTransfer.zip b/fileTransfer/fileTransfer.zip new file mode 100644 index 0000000..a5d94cb Binary files /dev/null and b/fileTransfer/fileTransfer.zip differ diff --git a/fileTransfer/info.json b/fileTransfer/info.json new file mode 100644 index 0000000..e6421e7 --- /dev/null +++ b/fileTransfer/info.json @@ -0,0 +1,10 @@ +{ + "id": "fileTransfer", + "version": 0.007, + "modules": [ + { + "id": "fileTransfer", + "version": 0 + } + ] +} \ No newline at end of file diff --git a/fspn/.gitignore b/fspn/.gitignore new file mode 100644 index 0000000..16cfb2f --- /dev/null +++ b/fspn/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +fspn.zip \ No newline at end of file diff --git a/fspn/.mtimes.json b/fspn/.mtimes.json new file mode 100644 index 0000000..2d194dc --- /dev/null +++ b/fspn/.mtimes.json @@ -0,0 +1 @@ +{".gitignore": 1741162475.4773676, "requirements.txt": 1740930861.0004706, "test.py": 1753436830.9918232, "protocol\\connection.py": 1757413256.9977374, "protocol\\security.py": 1757413451.9272285, "protocol\\server.py": 1757831912.1189482, "protocol\\__pycache__\\connection.cpython-311.pyc": 1753509446.7233407, "protocol\\__pycache__\\security.cpython-311.pyc": 1753436646.3835695, "protocol\\__pycache__\\server.cpython-311.pyc": 1739790368.10843, "utils\\aes_util.py": 1739790367.99551, "utils\\ecdh_util.py": 1739790367.9960146, "utils\\ecdsa_util.py": 1741151848.8236935, "utils\\observable.py": 1756021286.9012282, "utils\\sha256_util.py": 1752738996.6411796, "utils\\wrapper_util.py": 1739790368.001054, "utils\\__pycache__\\aes_util.cpython-311.pyc": 1739790368.1195576, "utils\\__pycache__\\aes_util.cpython-313.pyc": 1740069706.9769707, "utils\\__pycache__\\ecdh_util.cpython-311.pyc": 1739790368.1672213, "utils\\__pycache__\\ecdh_util.cpython-313.pyc": 1741381590.6166677, "utils\\__pycache__\\ecdsa_util.cpython-311.pyc": 1741380658.4493158, "utils\\__pycache__\\ecdsa_util.cpython-313.pyc": 1741381591.9633517, "utils\\__pycache__\\observable.cpython-311.pyc": 1753436646.3665586, "utils\\__pycache__\\sha256_util.cpython-311.pyc": 1752742523.9286432, "utils\\__pycache__\\sha256_util.cpython-313.pyc": 1744026927.3028462, "utils\\__pycache__\\wrappers.cpython-311.pyc": 1739790368.1048622, "utils\\__pycache__\\wrapper_util.cpython-311.pyc": 1740064619.98263, "utils\\__pycache__\\wrapper_util.cpython-313.pyc": 1741381591.983756} \ No newline at end of file diff --git a/fspn/info.json b/fspn/info.json new file mode 100644 index 0000000..ac8d38b --- /dev/null +++ b/fspn/info.json @@ -0,0 +1,5 @@ +{ + "id": "fspn", + "version": 0.007, + "modules": [] +} \ No newline at end of file diff --git a/fspn/protocol/connection.py b/fspn/protocol/connection.py new file mode 100644 index 0000000..1b650a6 --- /dev/null +++ b/fspn/protocol/connection.py @@ -0,0 +1,264 @@ +from ..utils.observable import Observable +from ..utils.wrapper_util import threaded +from .security import Security + +from enum import Enum +import socket +import ipaddress +import struct +import time, datetime +import logging, traceback +import json +import uuid + +# TODO Impossible: Hiding ip in a p2p connection hahahahahaha + +HEADER_STRUCTURE = '!I?IIb' # size, encrypted, nonce_len, mac_len, is_binary +HEADER_SIZE = struct.calcsize(HEADER_STRUCTURE) # 14 bytes + +MAX_CONNECTION_TRIES = 3 + +class EVENTS(Enum): + ON_CONNECTION = 0 + ON_CONNECTION_ERROR = 1 + ON_DISCONNECTION = 2 + ON_MESSAGE = 3 + +events = [EVENTS.ON_CONNECTION, EVENTS.ON_CONNECTION_ERROR, EVENTS.ON_DISCONNECTION, EVENTS.ON_MESSAGE] + +class STATUS(Enum): + DISCONNECTED = 0 + CONNECTED = 1 + CONNECTING = 2 + HANDSHAKING = 3 + ERROR = -1 + +class Connection(Observable): + def __init__(self, user, pmc, conn=None): + super().__init__(events) + self.security = Security(user, pmc) + self.status = STATUS.DISCONNECTED + self.address = None + self.hostname = None + self.bind_address = None + self.conn = conn + if conn: + self.set_addresses() + self.id = str(uuid.uuid4()) + self.handshake_payload = None + + def set_addresses(self, address=None): + if address: + self.address = address + host, port = address + try: + ipaddress.ip_address(host) + except ValueError: + self.hostname = host + else: + self.address = self.conn.getpeername() + self.bind_address = self.conn.getsockname() + + + @threaded + def connect(self, address, bind_address=('0.0.0.0', 0)): + self.set_addresses(address) + self.bind_address = bind_address + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + #s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + s.bind(bind_address) + s.settimeout(10) + self.conn = s + except Exception: + self.status = STATUS.ERROR + self.fire_event(EVENTS.ON_CONNECTION_ERROR, error="Error to setup connection") + raise + + self.status = STATUS.CONNECTING + logging.info(f'Socket trying to connect: {self.bind_address} -> {address}') + + for i in range(MAX_CONNECTION_TRIES): + try: + s.settimeout(None) + self.conn = s + self.bind_address = s.getsockname() + s.connect(address) + self.address = s.getpeername() + break + except Exception as e: + logging.exception("ERROR") + if i < MAX_CONNECTION_TRIES - 1: + continue + else: + self.status = STATUS.ERROR + self.fire_event(EVENTS.ON_CONNECTION_ERROR, error=f"No connection could be made in {MAX_CONNECTION_TRIES} retries: {e}") + raise + + self.new_connection() + + def new_connection(self): + self.handshake_create_payload() + + def handshake_create_payload(self): + self.status = STATUS.HANDSHAKING + logging.info(f'Socket handshaking: {self.bind_address} -> {self.address}') + my_ecdsa_str = self.security.user + my_proof_of_work = self.security.proof_of_work + my_ecdh_pk = self.security.ecdh.public_key_to_str() + date = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + self.handshake_payload = {'ecdsa':my_ecdsa_str, 'pof':my_proof_of_work, 'ecdh':my_ecdh_pk, 'date':date} + payload = {'ecdsa':my_ecdsa_str, 'pof':my_proof_of_work, 'ecdh':my_ecdh_pk, 'date':date} + request_id = self.security.sign_message_ecdsa(json.dumps(payload), self.payload_signature_callback, "Handshake Connection") + self.wait_signature() + + @threaded + def wait_signature(self): + # TODO Config this time as security time validation + time.sleep(120) + if self.status == STATUS.HANDSHAKING: + logging.info(f"Closing connection, payload signature time exceded") + self.status = STATUS.DISCONNECTED + self.fire_event(EVENTS.ON_CONNECTION_ERROR, error="Payload signature time exceded") + self.close_connection() + + def payload_signature_callback(self, request_id, signature): + if self.status != STATUS.HANDSHAKING: + logging.info(f"Not using signature, connection is closed") + return None + if signature: + self.handshake_payload['signature'] = signature + self.send_message(json.dumps(self.handshake_payload), False) + + while self.status == STATUS.HANDSHAKING: + message = self.message_reader()["data"] + logging.error + if(message): + payload = json.loads(message) + payload_signed = payload.copy() + payload_signed.pop('signature') + self.security.handshake_validation(payload['ecdsa'], json.dumps(payload_signed), payload['signature'], payload['ecdh'], payload['pof'], payload['date']) + + self.status = STATUS.CONNECTED + logging.info(f'Ready: {self.bind_address} -> {self.address}') + self.fire_event(EVENTS.ON_CONNECTION) + self.wait_message() + else: + self.status = STATUS.DISCONNECTED + self.fire_event(EVENTS.ON_CONNECTION_ERROR, error="Payload signature is None") + self.close_connection() + + @threaded + def wait_message(self): + while self.status == STATUS.CONNECTED: + try: + message = self.message_reader() + except socket.timeout as to: + logging.exception("ERROR") + continue + except (ConnectionAbortedError, EOFError, ConnectionResetError, OSError): + # TODO Maybe try to reconnect + self.status = STATUS.DISCONNECTED + self.fire_event(EVENTS.ON_DISCONNECTION) + break + except Exception as e: + logging.exception("ERROR") + self.status = STATUS.DISCONNECTED + self.fire_event(EVENTS.ON_DISCONNECTION) + break + + try: + if(message): + # logging.debug(message) + self.fire_event(EVENTS.ON_MESSAGE, message=message) + except Exception as e: + logging.error(traceback.format_exc()) + + def close_connection(self): + logging.info(f'Socket closed: {self.address}') + self.conn.close() + self.status = STATUS.DISCONNECTED + self.fire_event(EVENTS.ON_DISCONNECTION) + + @threaded + def send_binary(self, data: bytes, meta: dict = None, encrypted=True): + meta_json = json.dumps(meta or {}) + meta_encoded = meta_json.encode('utf-8') + meta_len_bytes = struct.pack('!I', len(meta_encoded)) + full_payload = meta_len_bytes + meta_encoded + data + + nonce = b'' + mac = b'' + + if encrypted: + nonce, full_payload, mac = self.security.encrypt_message(full_payload) + + message_header = struct.pack( + HEADER_STRUCTURE, + len(nonce) + len(mac) + len(full_payload), + encrypted, + len(nonce), + len(mac), + 1 # is_binary = True + ) + + self.conn.sendall(message_header + nonce + mac + full_payload) + + @threaded + def send_message(self, message: str, encrypted=True): + nonce = b'' + mac = b'' + meta = b'' + encoded_msg = message.encode('utf-8') + + if encrypted: + nonce, encoded_msg, mac = self.security.encrypt_message(encoded_msg) + + message_header = struct.pack( + HEADER_STRUCTURE, + len(nonce) + len(mac) + len(meta) + len(encoded_msg), + encrypted, + len(nonce), + len(mac), + 0 # is_binary = False + ) + + self.conn.sendall(message_header + nonce + mac + encoded_msg) + + + def message_reader(self): + message_header = self.recv_all(HEADER_SIZE) + if not message_header: + self.close_connection() + return None + + total_len, encrypted, nonce_len, mac_len, is_binary = struct.unpack(HEADER_STRUCTURE, message_header) + data = self.recv_all(total_len) + + if encrypted: + nonce = data[:nonce_len] + mac = data[nonce_len:nonce_len + mac_len] + payload = data[nonce_len + mac_len:] + decrypted = self.security.decrypt_message(nonce, payload, mac) + else: + decrypted = data + + if is_binary: + meta_length = struct.unpack('!I', decrypted[:4])[0] + meta_raw = decrypted[4:4 + meta_length] + meta = json.loads(meta_raw) + file_data = decrypted[4 + meta_length:] + return {"meta": meta, "data": file_data} + else: + return {"data": decrypted.decode('utf-8')} + + + def recv_all(self, n: int) -> bytes: + buffer = b'' + while len(buffer) < n: + chunk = self.conn.recv(n - len(buffer)) + if not chunk: + raise ConnectionError("Connection closed before receive all bytes") + buffer += chunk + return buffer diff --git a/fspn/protocol/security.py b/fspn/protocol/security.py new file mode 100644 index 0000000..09dc1da --- /dev/null +++ b/fspn/protocol/security.py @@ -0,0 +1,112 @@ +from ..utils.observable import Observable +from ..utils.wrapper_util import singleton +from ..utils import sha256_util, aes_util, ecdh_util, ecdsa_util + +import base64 +import logging +import importlib + +# class EcdsaKey: +# def __init__(self) -> None: +# self.verifying:ecdsa_util.VerifyingKey = None +# self.signing:ecdsa_util.SigningKey = None + +# def create_key_from_string(self, password:str): +# self.verifying, self.signing = ecdsa_util.create_keys(password.encode()) + +# def create_key_from_bytes(self, password:bytes): +# self.verifying, self.signing = ecdsa_util.create_keys(password) + +# def load_verifying(self, key:str): +# self.verifying = ecdsa_util.load_verifying_key(base64.b64decode(key.encode())) + +# def verifying_key_to_str(self): +# return base64.b64encode(self.verifying.to_string('compressed')).decode() + +class UserData: + def __init__(self): + self.proof_of_work = None + +# Password Manager Client +class Pmc: + def raiseException(self): + raise Exception("Missing Password Manager Client") + + def get(self, user) -> UserData: + self.raiseException() + + # Returns a request_id. Callback receives str:request_id str:signature + def sign(self, data, user, callback, info=None) -> str: + self.raiseException() + +class EcdhKey: + def __init__(self): + self.public, self.private = ecdh_util.generate_keys() + self.derived_key = None + + def generate_derived_key(self, peer_key:str): + ecdh_pk = ecdh_util.load_public_key_str(peer_key, True) + shared_key = ecdh_util.generate_shared_key(self.private, ecdh_pk) + self.derived_key = ecdh_util.generate_derived_key(shared_key) + + def update_derived_key(self): + self.derived_key = ecdh_util.generate_derived_key(self.derived_key) + + def public_key_to_str(self): + return ecdh_util.public_key_to_str(self.public, True) + +class Security(): + def __init__(self, user, pmc:Pmc): + self.pmc = pmc + self.user = user + self.proof_of_work = self.pmc.get(user).proof_of_work + + self.peer_user = None + self.peer_ecdsa = None + self.ecdh = EcdhKey() + + self.peer_pof_level = None + self.min_proof_of_work_level = 4 + + def encrypt_message(self, message:bytes): + return aes_util.encrypt(message, self.ecdh.derived_key) + + def decrypt_message(self, nonce:bytes, message:bytes, mac:bytes): + return aes_util.decrypt_and_verify(nonce, message, mac, self.ecdh.derived_key) + + def sign_message_ecdsa(self, message:str, callback, info=None): + hash_message = sha256_util.hash_string(message) + return self.pmc.sign(hash_message, self.user, callback, info) + # return base64.b64encode(ecdsa_util.sign_message(message, self.my_ecdsa.signing)).decode() + + def check_signature_ecdsa(self, message:str, signature:str): + hash_message = sha256_util.hash_string(message) + return ecdsa_util.verify_message(hash_message.encode(), base64.b64decode(signature.encode()), self.peer_ecdsa) + + def check_signature_ecdsa_vk(self, verifying_key:str, message:str, signature:str): + hash_message = sha256_util.hash_string(message) + return ecdsa_util.verify_message(hash_message.encode(), base64.b64decode(signature.encode()), ecdsa_util.load_verifying_key(base64.b64decode(verifying_key.encode()))) + + # def encrypt_message_ecdsa(self, message:str): + # encrypted = ecdsa_util.encrypt_message(base64.b64decode(self.peer_ecdsa.verifying_key_to_str().encode()), message.encode()) + # return base64.b64encode(encrypted).decode() + + # def decrypt_message_ecdsa(self, message:str): + # return ecdsa_util.decrypt_message(self.my_ecdsa.signing.to_string(), base64.b64decode(message.encode())) + + + def verify_proof_of_work(self, ecdsa:str, proof_of_work:str): + hash = sha256_util.hash_string(f'{ecdsa}{proof_of_work}') + logging.debug(f'ECDSA {ecdsa}, POF {proof_of_work}, HASH {hash}') + if hash[0:self.min_proof_of_work_level] != "0"*self.min_proof_of_work_level: + raise Exception(f'Proof of work below minimum level {self.min_proof_of_work_level}') + + # TODO validate date/time + def handshake_validation(self, peer_ecdsa, payload, payload_signature, ecdh, proof_of_work, date): + self.peer_user = peer_ecdsa + self.peer_ecdsa = ecdsa_util.load_verifying_key(base64.b64decode(peer_ecdsa.encode())) + self.check_signature_ecdsa(payload, payload_signature) + + self.verify_proof_of_work(peer_ecdsa, proof_of_work) + self.ecdh.generate_derived_key(ecdh) + diff --git a/fspn/protocol/server.py b/fspn/protocol/server.py new file mode 100644 index 0000000..c939d89 --- /dev/null +++ b/fspn/protocol/server.py @@ -0,0 +1,74 @@ + +from ..utils.observable import Observable, Event as ObservableEvent +from ..utils.wrapper_util import threaded +from .connection import Connection, EVENTS as CONNECTION_EVENTS + +from enum import Enum +import logging, traceback +import socket +import random + +class EVENTS(Enum): + ON_START = 0 + ON_START_ERROR = 1 + ON_CONNECTION = 2 + ON_CONNECTION_ERROR = 3 + ON_DISCONNECTION = 4 + ON_MESSAGE = 5 + +class Server(Observable): + def __init__(self): + super().__init__() + self.connections:dict[tuple[str,int],Connection] = {} + self.bind_address = None + self.running = False + self.user = None + + @threaded + def run(self, user, pmc, bind_address = ('127.0.0.1', random.randint(5000, 5999))): + try: + self.user = user + if not bind_address: + self.bind_address = ('127.0.0.1', random.randint(5000, 5999)) + else: + self.bind_address = bind_address + logging.info(f"Starting server on address {self.bind_address}") + + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(self.bind_address) + s.settimeout(10) + s.listen(5) + self.running = True + self.fire_event(EVENTS.ON_START) + logging.info(f"Listening on {self.bind_address}") + + while True: + try: + conn, addr = s.accept() + logging.info(f"Incoming connection: {addr}") + connection = Connection(user, pmc, conn) + connection.subscribe_event(CONNECTION_EVENTS.ON_CONNECTION, self.on_server_connection) + connection.subscribe_event(CONNECTION_EVENTS.ON_MESSAGE, self.on_server_message) + connection.subscribe_event(CONNECTION_EVENTS.ON_DISCONNECTION, self.on_server_disconnection) + self.connections[addr] = connection + self.connections[addr].new_connection() + except socket.timeout: + continue + except Exception: + logging.error("ERROR") + conn.close() + + except Exception as e: + logging.error("ERROR") + self.fire_event(EVENTS.ON_START_ERROR, error=e) + + + def on_server_connection(self, event:ObservableEvent): + pass + + def on_server_message(self, event): + pass + + def on_server_disconnection(self, event): + pass diff --git a/fspn/requirements.txt b/fspn/requirements.txt new file mode 100644 index 0000000..c80b102 --- /dev/null +++ b/fspn/requirements.txt @@ -0,0 +1,3 @@ +pycryptodome +cryptography +ecdsa \ No newline at end of file diff --git a/fspn/test.py b/fspn/test.py new file mode 100644 index 0000000..d3a6add --- /dev/null +++ b/fspn/test.py @@ -0,0 +1,78 @@ +import os, sys +root_dir = os.path.normpath(__file__.split("libs")[0]) +sys.path.append(root_dir) + +from libs.fspn.protocol.connection import Connection, EVENTS +from libs.fspn.utils import sha256_util, aes_util, ecdh_util, ecdsa_util +from libs.fspn.utils.wrapper_util import singleton, threaded +import base64, time + +class UserData: + def __init__(self): + self.proof_of_work = None + +class Pmc: + def __init__(self): + self.verifying_key, self.signing_key = ecdsa_util.create_keys(base64.b64decode("I0x1Y2FzR2FicmllbFZhekRvc1NhbnRvc0luYWNpbyE=")) + + def get(self, user) -> UserData: + user_data = UserData() + user_data.proof_of_work = "eYnU*@" + return user_data + + # Returns a request_id. Callback receives str:request_id str:signature + def sign(self, data, user, callback) -> str: + self.send_callback("test", callback, data) + return "test" + + def send_callback(self, request_id, callback, data): + signature = ecdsa_util.sign_message(data, self.signing_key) + signature = base64.b64encode(signature).decode() + callback(request_id, signature) + + +class Test: + def __init__(self): + pass + + def test(self): + pmc = Pmc() + + c1 = Connection("A4DZSk+TlR+4w39MbiIAQbti+N0H1QlJEhRH2DI6Iubj", pmc) + c2 = Connection("A4DZSk+TlR+4w39MbiIAQbti+N0H1QlJEhRH2DI6Iubj", pmc) + + p1= 5676 + p2 = 5686 + c1.connect(("127.0.0.1", p1), ("127.0.0.1", p2)) + c2.connect(("127.0.0.1", p2), ("127.0.0.1", p1)) + + c1.subscribe_event(EVENTS.ON_MESSAGE, self.on_message_1) + c2.subscribe_event(EVENTS.ON_MESSAGE, self.on_message_2) + + time.sleep(2) + + c1.send_message("Testing", True) + c1.send_binary(data=b"Test", meta={"test":"test"}, encrypted=True) + + time.sleep(2) + + c1.close_connection() + c2.close_connection() + + def on_message_1(self, event): + print("C1 got message: ",event.__dict__) + + def on_message_2(self, event): + print("C2 got message: ",event.__dict__) + +import logging +import sys + +logging.basicConfig( + level=logging.DEBUG, + format='[%(levelname)s] %(asctime)s - %(message)s', + stream=sys.stdout +) + +t = Test() +t.test() \ No newline at end of file diff --git a/fspn/utils/aes_util.py b/fspn/utils/aes_util.py new file mode 100644 index 0000000..4742d29 --- /dev/null +++ b/fspn/utils/aes_util.py @@ -0,0 +1,17 @@ +from Crypto.Cipher import AES + +def encrypt(data:bytes, key:bytes): + cipher = AES.new(key, AES.MODE_EAX) + nonce = cipher.nonce + ciphertext, mac = cipher.encrypt_and_digest(data) + return nonce, ciphertext, mac + +def decrypt_and_verify(nonce:bytes, data:bytes, mac:bytes, key:bytes): + cipher = AES.new(key, AES.MODE_EAX, nonce=nonce) + plaintext = cipher.decrypt(data) + try: + cipher.verify(mac) + except ValueError: + return None + return plaintext + diff --git a/fspn/utils/ecdh_util.py b/fspn/utils/ecdh_util.py new file mode 100644 index 0000000..f9b4caf --- /dev/null +++ b/fspn/utils/ecdh_util.py @@ -0,0 +1,36 @@ +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.kdf.hkdf import HKDF +from cryptography.hazmat.primitives.serialization import PublicFormat, Encoding, load_pem_public_key + +def generate_keys(): + private_key = ec.generate_private_key( + ec.SECP384R1() + ) + public_key = private_key.public_key() + return public_key, private_key + +def generate_shared_key(private_key:ec.EllipticCurvePrivateKey, public_key:ec.EllipticCurvePublicKey): + shared_key = private_key.exchange(ec.ECDH(), public_key) + return shared_key + +def generate_derived_key(shared_key:bytes): + derived_key = HKDF( + algorithm=hashes.SHA256(), + length=32, + salt=None, + info=None, + ).derive(shared_key) + return derived_key + +def public_key_to_str(public_key:ec.EllipticCurvePublicKey, remove_header_and_footer=False): + public_key_str = public_key.public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo).decode() + if(remove_header_and_footer): + public_key_str = public_key_str.replace('-----BEGIN PUBLIC KEY-----\n','') + public_key_str = public_key_str.replace('\n-----END PUBLIC KEY-----\n','') + return public_key_str + +def load_public_key_str(public_key_str:str, removed_header_and_footer=False): + if(removed_header_and_footer): + public_key_str = f'-----BEGIN PUBLIC KEY-----\n{public_key_str}\n-----END PUBLIC KEY-----\n' + return load_pem_public_key(public_key_str.encode()) diff --git a/fspn/utils/ecdsa_util.py b/fspn/utils/ecdsa_util.py new file mode 100644 index 0000000..cb7703d --- /dev/null +++ b/fspn/utils/ecdsa_util.py @@ -0,0 +1,62 @@ +from ecdsa import SigningKey, VerifyingKey, SECP256k1, keys +from hashlib import sha256 +# from ecies import encrypt, decrypt + +def create_keys(password:bytes) -> tuple[VerifyingKey, SigningKey]: # type: ignore + privateKey = SigningKey.from_string(password, curve=SECP256k1) + publicKey:VerifyingKey = privateKey.get_verifying_key() + return (publicKey, privateKey) + +def create_pem(fullpath, privateKey:SigningKey, publicKey:VerifyingKey): + with open(fullpath+"privateKey.pem", "wb") as f: + f.write(privateKey.to_pem(format="pkcs8")) + with open(fullpath+"publicKey.pem", "wb") as f: + f.write(publicKey.to_pem()) + +def read_pem(fullpath): + with open(fullpath+"privateKey.pem") as f: + privateKey = SigningKey.from_pem(f.read()) + with open(fullpath+"publicKey.pem") as f: + publicKey = VerifyingKey.from_pem(f.read()) + return (privateKey, publicKey) + +def sign_message(message:str, privateKey:SigningKey) -> bytes: + return privateKey.sign(message.encode('utf-8'), hashfunc=sha256) + +def load_signing_key(signing_key:bytes) -> SigningKey: + return SigningKey.from_string(signing_key, curve=SECP256k1, hashfunc=sha256) + +def get_verifying_key(signing_key:SigningKey) -> VerifyingKey: + return signing_key.get_verifying_key() + +def load_verifying_key(verifying_key:bytes) -> VerifyingKey: + return VerifyingKey.from_string(verifying_key, curve=SECP256k1, hashfunc=sha256) + +def verify_message(message:bytes, signature:bytes, publicKey:VerifyingKey) -> bool: + try: + return publicKey.verify(signature, message, hashfunc=sha256) + except keys.BadSignatureError: + return False + +# ecdsa_vk.to_string('compressed') or vk_bytes +# def encrypt_message(verifying_key:bytes, message:bytes): +# return encrypt(verifying_key, message) + +# ecdsa_sk.to_string() or sk_bytes +# def decrypt_message(signing_key:bytes, message:bytes): +# return decrypt(signing_key, message) + +# password = b'#LucasGabrielVazDosSantosInacio!' + +# vk,sk = create_keys(password) +# import base64 + +# print(base64.b64encode(vk.to_string('compressed')).decode()) + +# pk ='AuWgGLOi4VUxYQnZzcXqtzl1nA4H4MAL+fzLgjf+TX8C' +# message= '{"text":"Hello","public_key":"AuWgGLOi4VUxYQnZzcXqtzl1nA4H4MAL+fzLgjf+TX8C","pof":"69201","signature":null,"files":[],"networks":["000"],"datetime":"2024-10-06T07:08:30.419000Z","parents":[]}' +# sig = 'dRAyr67oZXblKEqS9EpghhS7mbl1DOoqCF8n8krQ2KsTcV9VRK6Hc4O2A27WkdjW2ZEaalp2PbPd1ZamAMJJ/A==' + +# pk = load_verifying_key(base64.b64decode(pk)) +# sig = base64.b64decode(sig) +# print(verify_message(message.encode(), sig, pk)) \ No newline at end of file diff --git a/fspn/utils/observable.py b/fspn/utils/observable.py new file mode 100644 index 0000000..a9a439a --- /dev/null +++ b/fspn/utils/observable.py @@ -0,0 +1,44 @@ +from .wrapper_util import threaded +import logging, traceback + +class Event(object): + def __init__(self): + self.source = self + +class Observable(object): + """ + A simple publish-subscribe system. + """ + + def __init__(self, events = []): + self.events: dict[str, list] = {} + for event in events: + self.register_event(event) + + def register_event(self, event): + if(event not in self.events): + self.events[event] = [] + + def subscribe_event(self, event, callback): + if(event not in self.events): + self.register_event(event) + self.events[event].append(callback) + + @threaded + def fire_event(self, event, **kwargs): + e = Event() + e.source = self + for k, v in kwargs.items(): + setattr(e, k, v) + if(event in self.events): + for fn in self.events[event]: + self.call_observer(fn, e) + else: + logging.warning(f"Event {event} without callback") + + @threaded + def call_observer(self, function, event): + try: + function(event) + except Exception as ex: + logging.exception(f'Error in event {event} to function {function.__name__}') \ No newline at end of file diff --git a/fspn/utils/sha256_util.py b/fspn/utils/sha256_util.py new file mode 100644 index 0000000..a65d2f2 --- /dev/null +++ b/fspn/utils/sha256_util.py @@ -0,0 +1,67 @@ +import hashlib + +def hash_file(filepath): + BUF_SIZE = 65 * 1024 + + sha = hashlib.sha256() + + with open(filepath, 'rb') as f: + while True: + data = f.read(BUF_SIZE) + if not data: + break + sha.update(data) + return sha.hexdigest() + +def hash_bytes(data): + sha = hashlib.sha256() + sha.update(data) + return sha.hexdigest() + +def hash_string(data:str): + sha = hashlib.sha256() + sha.update(str(data).encode('utf-8')) + return sha.hexdigest() + +# --------------- +import datetime +import string +import itertools +import random + +def get_nonce(characters, lenght): + yield from itertools.product(*([characters] * lenght)) + +def count_leading_zeros(text): + n = 0 + for i in range(len(text)): + if text[:i] == "0" * i: + n = i + else: + break + return n + +def mine_user(data, force=4, nonce_lenght=4): + characters = '[@_!#$%^&*()<>?/\|}{~:]'+string.ascii_letters+string.digits + while True: + for x in get_nonce(characters, nonce_lenght): + nonce = ''.join(x)+random.choice(characters) + hash = hash_string(data+ nonce) + leading_zeros = count_leading_zeros(hash) + if leading_zeros >= force: + return nonce + + +def test(): + print(datetime.datetime.now(), "EXECUTING HASH TEST - MINE USER") + public_key_str = "A4DZSk+TlR+4w39MbiIAQbti+N0H1QlJEhRH2DI6Iubj" + nonce, hash = mine_user(public_key_str) + + print(nonce, hash) + +# test() + +# A4DZSk+TlR+4w39MbiIAQbti+N0H1QlJEhRH2DI6Iubj +# 8 +# eYnU*@ +# [/Q#7r \ No newline at end of file diff --git a/fspn/utils/wrapper_util.py b/fspn/utils/wrapper_util.py new file mode 100644 index 0000000..6fd9002 --- /dev/null +++ b/fspn/utils/wrapper_util.py @@ -0,0 +1,19 @@ +import functools +from threading import Thread + +def threaded(fn): + """Decorator to automatically launch a function in a thread""" + @functools.wraps(fn) + def wrapper(*args, **kwargs): + thread = Thread(target=fn, args=args, kwargs=kwargs) + thread.start() + return thread + return wrapper + +def singleton(cls): + instances = {} + def getinstance(*args, **kwargs): + if cls not in instances: + instances[cls] = cls(*args, **kwargs) + return instances[cls] + return getinstance diff --git a/lockbox/.gitignore b/lockbox/.gitignore new file mode 100644 index 0000000..81ed25b --- /dev/null +++ b/lockbox/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +lockbox.zip \ No newline at end of file diff --git a/lockbox/.mtimes.json b/lockbox/.mtimes.json new file mode 100644 index 0000000..e49edae --- /dev/null +++ b/lockbox/.mtimes.json @@ -0,0 +1 @@ +{".gitignore": 1741162489.7420642, "api.http": 1750139941.0187607, "config.json": 1756106460.8736105, "lockboxClient.py": 1757415645.911955, "lockboxService.py": 1757490968.0438733, "lockboxServiceApi.py": 1757415119.6281, "miner.py": 1757325231.7886086, "minerApiBlueprint.py": 1757323989.6872394, "minerSocketio.py": 1757322038.733255, "requirements.txt": 1756036846.2401123, "userHistory.json": 1756014413.6882465, "utils.py": 1756014180.2178173, "browserExtension\\hello.html": 1740309144.9421954, "browserExtension\\hello_extensions.png": 1740305089.7709863, "browserExtension\\main.js": 1740309132.1250446, "browserExtension\\manifest.json": 1740308869.3939943, "browserExtension\\socketio.js": 1740305420.5022602, "frontend\\.gitignore": 1753127603.0183902, "frontend\\ca.pem": 1757146987.2444186, "frontend\\ca_key.pem": 1757146987.2449198, "frontend\\cert.pem": 1757146987.2669685, "frontend\\index.html": 1753127949.1451695, "frontend\\jsconfig.json": 1753127603.072253, "frontend\\key.pem": 1757146987.2669685, "frontend\\package-lock.json": 1753577901.326157, "frontend\\package.json": 1753264154.9925382, "frontend\\README.md": 1753127749.0499122, "frontend\\vite.config.js": 1753133376.4389508, "frontend\\webView.py": 1757273055.978937, "frontend\\.vscode\\extensions.json": 1753127603.0647097, "frontend\\.vscode\\settings.json": 1753127603.0858867, "frontend\\dist\\favicon.ico": 1753127603.0304024, "frontend\\dist\\index.html": 1757196785.0428426, "frontend\\dist\\assets\\index-BZcXFX0l.css": 1757196785.0428426, "frontend\\dist\\assets\\index-DLC-DzCB.js": 1757196785.043342, "frontend\\public\\favicon.ico": 1753127603.0304024, "frontend\\src\\App.vue": 1753578911.9599397, "frontend\\src\\main.js": 1753571177.8232467, "frontend\\src\\assets\\logo.svg": 1753127603.100169, "frontend\\src\\assets\\main.css": 1753134037.7614193, "frontend\\src\\components\\buttons\\Button.vue": 1754224356.4003878, "frontend\\src\\components\\buttons\\ToogleSwitch.vue": 1754224437.5005052, "frontend\\src\\components\\cards\\Card.vue": 1754222510.193816, "frontend\\src\\components\\cards\\CardContent.vue": 1753192461.7694707, "frontend\\src\\components\\cards\\CardDescription.vue": 1753138683.593848, "frontend\\src\\components\\cards\\CardFooter.vue": 1753138707.6460035, "frontend\\src\\components\\cards\\CardHeader.vue": 1753190956.6568618, "frontend\\src\\components\\cards\\CardTitle.vue": 1753147372.2295625, "frontend\\src\\components\\inputs\\InputText.vue": 1754224553.033484, "frontend\\src\\components\\labels\\Label.vue": 1753141147.9629083, "frontend\\src\\components\\tabs\\TabCreateUser.vue": 1754224498.9855795, "frontend\\src\\components\\tabs\\TabProofOfWork.vue": 1754224221.950291, "frontend\\src\\components\\tabs\\TabSigningRequests.vue": 1754224256.0454957, "frontend\\src\\components\\tabs\\TabUsers.vue": 1754224245.3672867, "frontend\\src\\plugins\\socketio.js": 1756116610.3355243, "frontend\\src\\stores\\auth.js": 1753260869.1848066, "frontend\\src\\views\\Home.vue": 1754224669.0649729, "vue\\dependencies.txt": 1757200759.1109006, "vue\\router.js": 1757197583.1419137, "vue\\api\\lockboxClientApi.js": 1757244004.148773, "vue\\api\\lockboxServiceApi.js": 1757198065.150491, "vue\\api\\socketEvents.js": 1757198163.0826175, "vue\\components\\tabs\\TabCreateUser.vue": 1757197963.0401194, "vue\\components\\tabs\\TabProofOfWork.vue": 1757496275.300655, "vue\\components\\tabs\\TabSigningRequests.vue": 1757829169.3566494, "vue\\components\\tabs\\TabUsers.vue": 1757496293.6970932, "vue\\stores\\auth.js": 1757202108.6815858, "vue\\views\\HomeView.vue": 1757243879.6857555, "__pycache__\\lockboxService.cpython-311.pyc": 1756014397.4503593, "__pycache__\\lockboxServiceApi.cpython-311.pyc": 1756014397.199929, "__pycache__\\miner.cpython-311.pyc": 1753400645.8070076, "__pycache__\\minerBlueprint.cpython-311.pyc": 1753578561.8273892, "__pycache__\\utils.cpython-311.pyc": 1756014259.7933002} \ No newline at end of file diff --git a/lockbox/api.http b/lockbox/api.http new file mode 100644 index 0000000..4b58a00 --- /dev/null +++ b/lockbox/api.http @@ -0,0 +1,24 @@ +@hostname = http://127.0.0.1 +@port = 5001 +@hostServer = {{hostname}}:{{port}} + +### +POST {{hostServer}}/users HTTP/1.1 +content-type: application/json + +{"password":"MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MTI=", "data":{"proof_of_work":"eYnU*@"}} +###{"password":"I0x1Y2FzR2FicmllbFZhekRvc1NhbnRvc0luYWNpbyE=", "data":{"proof_of_work":"eYnU*@"}} +### A4DZSk+TlR+4w39MbiIAQbti+N0H1QlJEhRH2DI6Iubj + + +### +GET {{hostServer}}/users + +### +GET {{hostServer}}/users/A14%2FRek5z78U1rYC%2BWLvU%2FifnsX43o0tjnexmYdlXsjY + +### +POST {{hostServer}}/users/A14%2FRek5z78U1rYC%2BWLvU%2FifnsX43o0tjnexmYdlXsjY/sign HTTP/1.1 +content-type: application/json + +{"data":"Test"} diff --git a/lockbox/browserExtension/hello.html b/lockbox/browserExtension/hello.html new file mode 100644 index 0000000..9d0fcc3 --- /dev/null +++ b/lockbox/browserExtension/hello.html @@ -0,0 +1,16 @@ + + + + Flask WebSocket Example + + + + + + + + + + + + diff --git a/lockbox/browserExtension/hello_extensions.png b/lockbox/browserExtension/hello_extensions.png new file mode 100644 index 0000000..32c6c4b Binary files /dev/null and b/lockbox/browserExtension/hello_extensions.png differ diff --git a/lockbox/browserExtension/main.js b/lockbox/browserExtension/main.js new file mode 100644 index 0000000..eeb82d7 --- /dev/null +++ b/lockbox/browserExtension/main.js @@ -0,0 +1,19 @@ + +var socket = io("http://127.0.0.1:5000"); +socket.on('connect', function() { + console.log('Connected to server'); +}); +socket.on('message', function(msg) { + console.log('Message received: ' + msg); + document.getElementById("result").innerHTML = msg +}); +socket.on('disconnect', function() { + console.log('Disconnected from server'); +}); +function sendMessage() { + var msg = document.getElementById('message').value; + socket.send(msg); + socket.emit('ola',msg); +} + +document.getElementById("btn").addEventListener("click", sendMessage); \ No newline at end of file diff --git a/lockbox/browserExtension/manifest.json b/lockbox/browserExtension/manifest.json new file mode 100644 index 0000000..3566de2 --- /dev/null +++ b/lockbox/browserExtension/manifest.json @@ -0,0 +1,10 @@ +{ + "name": "Hello Extensions", + "description": "Base Level Extension", + "version": "1.0", + "manifest_version": 3, + "action": { + "default_popup": "hello.html", + "default_icon": "hello_extensions.png" + } +} \ No newline at end of file diff --git a/lockbox/browserExtension/socketio.js b/lockbox/browserExtension/socketio.js new file mode 100644 index 0000000..2dee50a --- /dev/null +++ b/lockbox/browserExtension/socketio.js @@ -0,0 +1,6042 @@ +/*! + * Socket.IO v4.0.0 + * (c) 2014-2021 Guillermo Rauch + * Released under the MIT License. + */ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["io"] = factory(); + else + root["io"] = factory(); +})(self, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = "./build/index.js"); +/******/ }) +/************************************************************************/ +/******/ ({ + +/***/ "./build/index.js": +/*!************************!*\ + !*** ./build/index.js ***! + \************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.Socket = exports.io = exports.Manager = exports.protocol = void 0; + +var url_1 = __webpack_require__(/*! ./url */ "./build/url.js"); + +var manager_1 = __webpack_require__(/*! ./manager */ "./build/manager.js"); + +var socket_1 = __webpack_require__(/*! ./socket */ "./build/socket.js"); + +Object.defineProperty(exports, "Socket", { + enumerable: true, + get: function get() { + return socket_1.Socket; + } +}); + +var debug = __webpack_require__(/*! debug */ "./node_modules/debug/src/browser.js")("socket.io-client"); +/** + * Module exports. + */ + + +module.exports = exports = lookup; +/** + * Managers cache. + */ + +var cache = exports.managers = {}; + +function lookup(uri, opts) { + if (_typeof(uri) === "object") { + opts = uri; + uri = undefined; + } + + opts = opts || {}; + var parsed = url_1.url(uri, opts.path); + 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_1.Manager(source, opts); + } else { + if (!cache[id]) { + debug("new io instance for %s", source); + cache[id] = new manager_1.Manager(source, opts); + } + + io = cache[id]; + } + + if (parsed.query && !opts.query) { + opts.query = parsed.queryKey; + } + + return io.socket(parsed.path, opts); +} + +exports.io = lookup; +/** + * Protocol version. + * + * @public + */ + +var socket_io_parser_1 = __webpack_require__(/*! socket.io-parser */ "./node_modules/socket.io-parser/dist/index.js"); + +Object.defineProperty(exports, "protocol", { + enumerable: true, + get: function get() { + return socket_io_parser_1.protocol; + } +}); +/** + * `connect`. + * + * @param {String} uri + * @public + */ + +exports.connect = lookup; +/** + * Expose constructors for standalone build. + * + * @public + */ + +var manager_2 = __webpack_require__(/*! ./manager */ "./build/manager.js"); + +Object.defineProperty(exports, "Manager", { + enumerable: true, + get: function get() { + return manager_2.Manager; + } +}); + +/***/ }), + +/***/ "./build/manager.js": +/*!**************************!*\ + !*** ./build/manager.js ***! + \**************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.Manager = void 0; + +var eio = __webpack_require__(/*! engine.io-client */ "./node_modules/engine.io-client/lib/index.js"); + +var socket_1 = __webpack_require__(/*! ./socket */ "./build/socket.js"); + +var parser = __webpack_require__(/*! socket.io-parser */ "./node_modules/socket.io-parser/dist/index.js"); + +var on_1 = __webpack_require__(/*! ./on */ "./build/on.js"); + +var Backoff = __webpack_require__(/*! backo2 */ "./node_modules/backo2/index.js"); + +var typed_events_1 = __webpack_require__(/*! ./typed-events */ "./build/typed-events.js"); + +var debug = __webpack_require__(/*! debug */ "./node_modules/debug/src/browser.js")("socket.io-client:manager"); + +var Manager = /*#__PURE__*/function (_typed_events_1$Stric) { + _inherits(Manager, _typed_events_1$Stric); + + var _super = _createSuper(Manager); + + function Manager(uri, opts) { + var _this; + + _classCallCheck(this, Manager); + + _this = _super.call(this); + _this.nsps = {}; + _this.subs = []; + + if (uri && "object" === _typeof(uri)) { + opts = uri; + uri = undefined; + } + + opts = opts || {}; + opts.path = opts.path || "/socket.io"; + _this.opts = opts; + + _this.reconnection(opts.reconnection !== false); + + _this.reconnectionAttempts(opts.reconnectionAttempts || Infinity); + + _this.reconnectionDelay(opts.reconnectionDelay || 1000); + + _this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000); + + _this.randomizationFactor(opts.randomizationFactor || 0.5); + + _this.backoff = new Backoff({ + min: _this.reconnectionDelay(), + max: _this.reconnectionDelayMax(), + jitter: _this.randomizationFactor() + }); + + _this.timeout(null == opts.timeout ? 20000 : opts.timeout); + + _this._readyState = "closed"; + _this.uri = uri; + + var _parser = opts.parser || parser; + + _this.encoder = new _parser.Encoder(); + _this.decoder = new _parser.Decoder(); + _this._autoConnect = opts.autoConnect !== false; + if (_this._autoConnect) _this.open(); + return _this; + } + + _createClass(Manager, [{ + key: "reconnection", + value: function reconnection(v) { + if (!arguments.length) return this._reconnection; + this._reconnection = !!v; + return this; + } + }, { + key: "reconnectionAttempts", + value: function reconnectionAttempts(v) { + if (v === undefined) return this._reconnectionAttempts; + this._reconnectionAttempts = v; + return this; + } + }, { + key: "reconnectionDelay", + value: function reconnectionDelay(v) { + var _a; + + if (v === undefined) return this._reconnectionDelay; + this._reconnectionDelay = v; + (_a = this.backoff) === null || _a === void 0 ? void 0 : _a.setMin(v); + return this; + } + }, { + key: "randomizationFactor", + value: function randomizationFactor(v) { + var _a; + + if (v === undefined) return this._randomizationFactor; + this._randomizationFactor = v; + (_a = this.backoff) === null || _a === void 0 ? void 0 : _a.setJitter(v); + return this; + } + }, { + key: "reconnectionDelayMax", + value: function reconnectionDelayMax(v) { + var _a; + + if (v === undefined) return this._reconnectionDelayMax; + this._reconnectionDelayMax = v; + (_a = this.backoff) === null || _a === void 0 ? void 0 : _a.setMax(v); + return this; + } + }, { + key: "timeout", + value: function timeout(v) { + if (!arguments.length) return this._timeout; + this._timeout = v; + return this; + } + /** + * Starts trying to reconnect if reconnection is enabled and we have not + * started reconnecting yet + * + * @private + */ + + }, { + key: "maybeReconnectOnOpen", + value: function maybeReconnectOnOpen() { + // Only try to reconnect if it's the first time we're connecting + if (!this._reconnecting && this._reconnection && this.backoff.attempts === 0) { + // keeps reconnection from firing twice for the same reconnection loop + this.reconnect(); + } + } + /** + * Sets the current transport `socket`. + * + * @param {Function} fn - optional, callback + * @return self + * @public + */ + + }, { + key: "open", + value: function open(fn) { + var _this2 = this; + + debug("readyState %s", this._readyState); + if (~this._readyState.indexOf("open")) return this; + debug("opening %s", this.uri); + this.engine = eio(this.uri, this.opts); + var socket = this.engine; + var self = this; + this._readyState = "opening"; + this.skipReconnect = false; // emit `open` + + var openSubDestroy = on_1.on(socket, "open", function () { + self.onopen(); + fn && fn(); + }); // emit `error` + + var errorSub = on_1.on(socket, "error", function (err) { + debug("error"); + self.cleanup(); + self._readyState = "closed"; + + _this2.emitReserved("error", err); + + if (fn) { + fn(err); + } else { + // Only do this if there is no fn to handle the error + self.maybeReconnectOnOpen(); + } + }); + + if (false !== this._timeout) { + var timeout = this._timeout; + debug("connect attempt will timeout after %d", timeout); + + if (timeout === 0) { + openSubDestroy(); // prevents a race condition with the 'open' event + } // set timer + + + var timer = setTimeout(function () { + debug("connect attempt timed out after %d", timeout); + openSubDestroy(); + socket.close(); + socket.emit("error", new Error("timeout")); + }, timeout); + + if (this.opts.autoUnref) { + timer.unref(); + } + + this.subs.push(function subDestroy() { + clearTimeout(timer); + }); + } + + this.subs.push(openSubDestroy); + this.subs.push(errorSub); + return this; + } + /** + * Alias for open() + * + * @return self + * @public + */ + + }, { + key: "connect", + value: function connect(fn) { + return this.open(fn); + } + /** + * Called upon transport open. + * + * @private + */ + + }, { + key: "onopen", + value: function onopen() { + debug("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_1.on(socket, "ping", this.onping.bind(this)), on_1.on(socket, "data", this.ondata.bind(this)), on_1.on(socket, "error", this.onerror.bind(this)), on_1.on(socket, "close", this.onclose.bind(this)), on_1.on(this.decoder, "decoded", this.ondecoded.bind(this))); + } + /** + * Called upon a ping. + * + * @private + */ + + }, { + key: "onping", + value: function onping() { + this.emitReserved("ping"); + } + /** + * Called with data. + * + * @private + */ + + }, { + key: "ondata", + value: function ondata(data) { + this.decoder.add(data); + } + /** + * Called when parser fully decodes a packet. + * + * @private + */ + + }, { + key: "ondecoded", + value: function ondecoded(packet) { + this.emitReserved("packet", packet); + } + /** + * Called upon socket error. + * + * @private + */ + + }, { + key: "onerror", + value: function onerror(err) { + debug("error", err); + this.emitReserved("error", err); + } + /** + * Creates a new socket for the given `nsp`. + * + * @return {Socket} + * @public + */ + + }, { + key: "socket", + value: function socket(nsp, opts) { + var socket = this.nsps[nsp]; + + if (!socket) { + socket = new socket_1.Socket(this, nsp, opts); + this.nsps[nsp] = socket; + } + + return socket; + } + /** + * Called upon a socket close. + * + * @param socket + * @private + */ + + }, { + key: "_destroy", + value: function _destroy(socket) { + var nsps = Object.keys(this.nsps); + + for (var _i = 0, _nsps = nsps; _i < _nsps.length; _i++) { + var nsp = _nsps[_i]; + var _socket = this.nsps[nsp]; + + if (_socket.active) { + debug("socket %s is still active, skipping close", nsp); + return; + } + } + + this._close(); + } + /** + * Writes a packet. + * + * @param packet + * @private + */ + + }, { + key: "_packet", + value: function _packet(packet) { + debug("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 + */ + + }, { + key: "cleanup", + value: function cleanup() { + debug("cleanup"); + this.subs.forEach(function (subDestroy) { + return subDestroy(); + }); + this.subs.length = 0; + this.decoder.destroy(); + } + /** + * Close the current socket. + * + * @private + */ + + }, { + key: "_close", + value: function _close() { + debug("disconnect"); + this.skipReconnect = true; + this._reconnecting = false; + + if ("opening" === this._readyState) { + // `onclose` will not fire because + // an open event never happened + this.cleanup(); + } + + this.backoff.reset(); + this._readyState = "closed"; + if (this.engine) this.engine.close(); + } + /** + * Alias for close() + * + * @private + */ + + }, { + key: "disconnect", + value: function disconnect() { + return this._close(); + } + /** + * Called upon engine close. + * + * @private + */ + + }, { + key: "onclose", + value: function onclose(reason) { + debug("onclose"); + this.cleanup(); + this.backoff.reset(); + this._readyState = "closed"; + this.emitReserved("close", reason); + + if (this._reconnection && !this.skipReconnect) { + this.reconnect(); + } + } + /** + * Attempt a reconnection. + * + * @private + */ + + }, { + key: "reconnect", + value: function reconnect() { + var _this3 = this; + + if (this._reconnecting || this.skipReconnect) return this; + var self = this; + + if (this.backoff.attempts >= this._reconnectionAttempts) { + debug("reconnect failed"); + this.backoff.reset(); + this.emitReserved("reconnect_failed"); + this._reconnecting = false; + } else { + var delay = this.backoff.duration(); + debug("will wait %dms before reconnect attempt", delay); + this._reconnecting = true; + var timer = setTimeout(function () { + if (self.skipReconnect) return; + debug("attempting reconnect"); + + _this3.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("reconnect attempt error"); + self._reconnecting = false; + self.reconnect(); + + _this3.emitReserved("reconnect_error", err); + } else { + debug("reconnect success"); + self.onreconnect(); + } + }); + }, delay); + + if (this.opts.autoUnref) { + timer.unref(); + } + + this.subs.push(function subDestroy() { + clearTimeout(timer); + }); + } + } + /** + * Called upon successful reconnect. + * + * @private + */ + + }, { + key: "onreconnect", + value: function onreconnect() { + var attempt = this.backoff.attempts; + this._reconnecting = false; + this.backoff.reset(); + this.emitReserved("reconnect", attempt); + } + }]); + + return Manager; +}(typed_events_1.StrictEventEmitter); + +exports.Manager = Manager; + +/***/ }), + +/***/ "./build/on.js": +/*!*********************!*\ + !*** ./build/on.js ***! + \*********************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.on = void 0; + +function on(obj, ev, fn) { + obj.on(ev, fn); + return function subDestroy() { + obj.off(ev, fn); + }; +} + +exports.on = on; + +/***/ }), + +/***/ "./build/socket.js": +/*!*************************!*\ + !*** ./build/socket.js ***! + \*************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +function _createForOfIteratorHelper(o, allowArrayLike) { var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } + +function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } + +function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); } + +function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.Socket = void 0; + +var socket_io_parser_1 = __webpack_require__(/*! socket.io-parser */ "./node_modules/socket.io-parser/dist/index.js"); + +var on_1 = __webpack_require__(/*! ./on */ "./build/on.js"); + +var typed_events_1 = __webpack_require__(/*! ./typed-events */ "./build/typed-events.js"); + +var debug = __webpack_require__(/*! debug */ "./node_modules/debug/src/browser.js")("socket.io-client:socket"); +/** + * 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 +}); + +var Socket = /*#__PURE__*/function (_typed_events_1$Stric) { + _inherits(Socket, _typed_events_1$Stric); + + var _super = _createSuper(Socket); + + /** + * `Socket` constructor. + * + * @public + */ + function Socket(io, nsp, opts) { + var _this; + + _classCallCheck(this, Socket); + + _this = _super.call(this); + _this.receiveBuffer = []; + _this.sendBuffer = []; + _this.ids = 0; + _this.acks = {}; + _this.flags = {}; + _this.io = io; + _this.nsp = nsp; + _this.ids = 0; + _this.acks = {}; + _this.receiveBuffer = []; + _this.sendBuffer = []; + _this.connected = false; + _this.disconnected = true; + _this.flags = {}; + + if (opts && opts.auth) { + _this.auth = opts.auth; + } + + if (_this.io._autoConnect) _this.open(); + return _this; + } + /** + * Subscribe to open, close and packet events + * + * @private + */ + + + _createClass(Socket, [{ + key: "subEvents", + value: function subEvents() { + if (this.subs) return; + var io = this.io; + this.subs = [on_1.on(io, "open", this.onopen.bind(this)), on_1.on(io, "packet", this.onpacket.bind(this)), on_1.on(io, "error", this.onerror.bind(this)), on_1.on(io, "close", this.onclose.bind(this))]; + } + /** + * Whether the Socket will try to reconnect when its Manager connects or reconnects + */ + + }, { + key: "connect", + + /** + * "Opens" the socket. + * + * @public + */ + value: function connect() { + if (this.connected) return this; + this.subEvents(); + if (!this.io["_reconnecting"]) this.io.open(); // ensure open + + if ("open" === this.io._readyState) this.onopen(); + return this; + } + /** + * Alias for connect() + */ + + }, { + key: "open", + value: function open() { + return this.connect(); + } + /** + * Sends a `message` event. + * + * @return self + * @public + */ + + }, { + key: "send", + value: function send() { + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + args.unshift("message"); + this.emit.apply(this, args); + return this; + } + /** + * Override `emit`. + * If the event is in `events`, it's emitted normally. + * + * @return self + * @public + */ + + }, { + key: "emit", + value: function emit(ev) { + if (RESERVED_EVENTS.hasOwnProperty(ev)) { + throw new Error('"' + ev + '" 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); + var packet = { + type: socket_io_parser_1.PacketType.EVENT, + data: args + }; + packet.options = {}; + packet.options.compress = this.flags.compress !== false; // event ack callback + + if ("function" === typeof args[args.length - 1]) { + debug("emitting packet with ack id %d", this.ids); + this.acks[this.ids] = args.pop(); + packet.id = this.ids++; + } + + var isTransportWritable = this.io.engine && this.io.engine.transport && this.io.engine.transport.writable; + var discardPacket = this.flags["volatile"] && (!isTransportWritable || !this.connected); + + if (discardPacket) { + debug("discard packet as the transport is not currently writable"); + } else if (this.connected) { + this.packet(packet); + } else { + this.sendBuffer.push(packet); + } + + this.flags = {}; + return this; + } + /** + * Sends a packet. + * + * @param packet + * @private + */ + + }, { + key: "packet", + value: function packet(_packet) { + _packet.nsp = this.nsp; + + this.io._packet(_packet); + } + /** + * Called upon engine `open`. + * + * @private + */ + + }, { + key: "onopen", + value: function onopen() { + var _this2 = this; + + debug("transport is open - connecting"); + + if (typeof this.auth == "function") { + this.auth(function (data) { + _this2.packet({ + type: socket_io_parser_1.PacketType.CONNECT, + data: data + }); + }); + } else { + this.packet({ + type: socket_io_parser_1.PacketType.CONNECT, + data: this.auth + }); + } + } + /** + * Called upon engine or manager `error`. + * + * @param err + * @private + */ + + }, { + key: "onerror", + value: function onerror(err) { + if (!this.connected) { + this.emitReserved("connect_error", err); + } + } + /** + * Called upon engine `close`. + * + * @param reason + * @private + */ + + }, { + key: "onclose", + value: function onclose(reason) { + debug("close (%s)", reason); + this.connected = false; + this.disconnected = true; + delete this.id; + this.emitReserved("disconnect", reason); + } + /** + * Called with socket packet. + * + * @param packet + * @private + */ + + }, { + key: "onpacket", + value: function onpacket(packet) { + var sameNamespace = packet.nsp === this.nsp; + if (!sameNamespace) return; + + switch (packet.type) { + case socket_io_parser_1.PacketType.CONNECT: + if (packet.data && packet.data.sid) { + var id = packet.data.sid; + this.onconnect(id); + } 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 socket_io_parser_1.PacketType.EVENT: + this.onevent(packet); + break; + + case socket_io_parser_1.PacketType.BINARY_EVENT: + this.onevent(packet); + break; + + case socket_io_parser_1.PacketType.ACK: + this.onack(packet); + break; + + case socket_io_parser_1.PacketType.BINARY_ACK: + this.onack(packet); + break; + + case socket_io_parser_1.PacketType.DISCONNECT: + this.ondisconnect(); + break; + + case socket_io_parser_1.PacketType.CONNECT_ERROR: + var err = new Error(packet.data.message); // @ts-ignore + + err.data = packet.data.data; + this.emitReserved("connect_error", err); + break; + } + } + /** + * Called upon a server event. + * + * @param packet + * @private + */ + + }, { + key: "onevent", + value: function onevent(packet) { + var args = packet.data || []; + debug("emitting event %j", args); + + if (null != packet.id) { + debug("attaching ack callback to event"); + args.push(this.ack(packet.id)); + } + + if (this.connected) { + this.emitEvent(args); + } else { + this.receiveBuffer.push(Object.freeze(args)); + } + } + }, { + key: "emitEvent", + value: function emitEvent(args) { + if (this._anyListeners && this._anyListeners.length) { + var listeners = this._anyListeners.slice(); + + var _iterator = _createForOfIteratorHelper(listeners), + _step; + + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var listener = _step.value; + listener.apply(this, args); + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } + } + + _get(_getPrototypeOf(Socket.prototype), "emit", this).apply(this, args); + } + /** + * Produces an ack callback to emit with an event. + * + * @private + */ + + }, { + key: "ack", + value: function ack(id) { + var self = this; + var sent = false; + return function () { + // prevent double callbacks + if (sent) return; + sent = true; + + for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + args[_key3] = arguments[_key3]; + } + + debug("sending ack %j", args); + self.packet({ + type: socket_io_parser_1.PacketType.ACK, + id: id, + data: args + }); + }; + } + /** + * Called upon a server acknowlegement. + * + * @param packet + * @private + */ + + }, { + key: "onack", + value: function onack(packet) { + var ack = this.acks[packet.id]; + + if ("function" === typeof ack) { + debug("calling ack %s with %j", packet.id, packet.data); + ack.apply(this, packet.data); + delete this.acks[packet.id]; + } else { + debug("bad ack %s", packet.id); + } + } + /** + * Called upon server connect. + * + * @private + */ + + }, { + key: "onconnect", + value: function onconnect(id) { + debug("socket connected with id %s", id); + this.id = id; + this.connected = true; + this.disconnected = false; + this.emitReserved("connect"); + this.emitBuffered(); + } + /** + * Emit buffered events (received and emitted). + * + * @private + */ + + }, { + key: "emitBuffered", + value: function emitBuffered() { + var _this3 = this; + + this.receiveBuffer.forEach(function (args) { + return _this3.emitEvent(args); + }); + this.receiveBuffer = []; + this.sendBuffer.forEach(function (packet) { + return _this3.packet(packet); + }); + this.sendBuffer = []; + } + /** + * Called upon server disconnect. + * + * @private + */ + + }, { + key: "ondisconnect", + value: function ondisconnect() { + debug("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 + */ + + }, { + key: "destroy", + value: function destroy() { + if (this.subs) { + // clean subscriptions to avoid reconnections + this.subs.forEach(function (subDestroy) { + return subDestroy(); + }); + this.subs = undefined; + } + + this.io["_destroy"](this); + } + /** + * Disconnects the socket manually. + * + * @return self + * @public + */ + + }, { + key: "disconnect", + value: function disconnect() { + if (this.connected) { + debug("performing disconnect (%s)", this.nsp); + this.packet({ + type: socket_io_parser_1.PacketType.DISCONNECT + }); + } // remove socket from pool + + + this.destroy(); + + if (this.connected) { + // fire events + this.onclose("io client disconnect"); + } + + return this; + } + /** + * Alias for disconnect() + * + * @return self + * @public + */ + + }, { + key: "close", + value: function close() { + return this.disconnect(); + } + /** + * Sets the compress flag. + * + * @param compress - if `true`, compresses the sending data + * @return self + * @public + */ + + }, { + key: "compress", + value: function compress(_compress) { + this.flags.compress = _compress; + return this; + } + /** + * Sets a modifier for a subsequent event emission that the event message will be dropped when this socket is not + * ready to send messages. + * + * @returns self + * @public + */ + + }, { + key: "onAny", + + /** + * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the + * callback. + * + * @param listener + * @public + */ + value: function onAny(listener) { + this._anyListeners = this._anyListeners || []; + + this._anyListeners.push(listener); + + return this; + } + /** + * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the + * callback. The listener is added to the beginning of the listeners array. + * + * @param listener + * @public + */ + + }, { + key: "prependAny", + value: function prependAny(listener) { + this._anyListeners = this._anyListeners || []; + + this._anyListeners.unshift(listener); + + return this; + } + /** + * Removes the listener that will be fired when any event is emitted. + * + * @param listener + * @public + */ + + }, { + key: "offAny", + value: function offAny(listener) { + if (!this._anyListeners) { + return this; + } + + if (listener) { + var listeners = this._anyListeners; + + for (var i = 0; i < listeners.length; i++) { + if (listener === listeners[i]) { + listeners.splice(i, 1); + return this; + } + } + } else { + this._anyListeners = []; + } + + return this; + } + /** + * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated, + * e.g. to remove listeners. + * + * @public + */ + + }, { + key: "listenersAny", + value: function listenersAny() { + return this._anyListeners || []; + } + }, { + key: "active", + get: function get() { + return !!this.subs; + } + }, { + key: "volatile", + get: function get() { + this.flags["volatile"] = true; + return this; + } + }]); + + return Socket; +}(typed_events_1.StrictEventEmitter); + +exports.Socket = Socket; + +/***/ }), + +/***/ "./build/typed-events.js": +/*!*******************************!*\ + !*** ./build/typed-events.js ***! + \*******************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); } + +function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.StrictEventEmitter = void 0; + +var Emitter = __webpack_require__(/*! component-emitter */ "./node_modules/component-emitter/index.js"); +/** + * Strictly typed version of an `EventEmitter`. A `TypedEventEmitter` takes type + * parameters for mappings of event names to event data types, and strictly + * types method calls to the `EventEmitter` according to these event maps. + * + * @typeParam ListenEvents - `EventsMap` of user-defined events that can be + * listened to with `on` or `once` + * @typeParam EmitEvents - `EventsMap` of user-defined events that can be + * emitted with `emit` + * @typeParam ReservedEvents - `EventsMap` of reserved events, that can be + * emitted by socket.io with `emitReserved`, and can be listened to with + * `listen`. + */ + + +var StrictEventEmitter = /*#__PURE__*/function (_Emitter) { + _inherits(StrictEventEmitter, _Emitter); + + var _super = _createSuper(StrictEventEmitter); + + function StrictEventEmitter() { + _classCallCheck(this, StrictEventEmitter); + + return _super.apply(this, arguments); + } + + _createClass(StrictEventEmitter, [{ + key: "on", + + /** + * Adds the `listener` function as an event listener for `ev`. + * + * @param ev Name of the event + * @param listener Callback function + */ + value: function on(ev, listener) { + _get(_getPrototypeOf(StrictEventEmitter.prototype), "on", this).call(this, ev, listener); + + return this; + } + /** + * Adds a one-time `listener` function as an event listener for `ev`. + * + * @param ev Name of the event + * @param listener Callback function + */ + + }, { + key: "once", + value: function once(ev, listener) { + _get(_getPrototypeOf(StrictEventEmitter.prototype), "once", this).call(this, ev, listener); + + return this; + } + /** + * Emits an event. + * + * @param ev Name of the event + * @param args Values to send to listeners of this event + */ + + }, { + key: "emit", + value: function emit(ev) { + var _get2; + + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + (_get2 = _get(_getPrototypeOf(StrictEventEmitter.prototype), "emit", this)).call.apply(_get2, [this, ev].concat(args)); + + return this; + } + /** + * Emits a reserved event. + * + * This method is `protected`, so that only a class extending + * `StrictEventEmitter` can emit its own reserved events. + * + * @param ev Reserved event name + * @param args Arguments to emit along with the event + */ + + }, { + key: "emitReserved", + value: function emitReserved(ev) { + var _get3; + + for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { + args[_key2 - 1] = arguments[_key2]; + } + + (_get3 = _get(_getPrototypeOf(StrictEventEmitter.prototype), "emit", this)).call.apply(_get3, [this, ev].concat(args)); + + return this; + } + /** + * Returns the listeners listening to an event. + * + * @param event Event name + * @returns Array of listeners subscribed to `event` + */ + + }, { + key: "listeners", + value: function listeners(event) { + return _get(_getPrototypeOf(StrictEventEmitter.prototype), "listeners", this).call(this, event); + } + }]); + + return StrictEventEmitter; +}(Emitter); + +exports.StrictEventEmitter = StrictEventEmitter; + +/***/ }), + +/***/ "./build/url.js": +/*!**********************!*\ + !*** ./build/url.js ***! + \**********************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.url = void 0; + +var parseuri = __webpack_require__(/*! parseuri */ "./node_modules/parseuri/index.js"); + +var debug = __webpack_require__(/*! debug */ "./node_modules/debug/src/browser.js")("socket.io-client:url"); +/** + * 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("protocol-less url %s", uri); + + if ("undefined" !== typeof loc) { + uri = loc.protocol + "//" + uri; + } else { + uri = "https://" + uri; + } + } // parse + + + debug("parse %s", uri); + obj = parseuri(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; +} + +exports.url = url; + +/***/ }), + +/***/ "./node_modules/backo2/index.js": +/*!**************************************!*\ + !*** ./node_modules/backo2/index.js ***! + \**************************************/ +/*! no static exports found */ +/***/ (function(module, exports) { + +/** + * Expose `Backoff`. + */ +module.exports = Backoff; +/** + * 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; +}; + +/***/ }), + +/***/ "./node_modules/component-emitter/index.js": +/*!*************************************************!*\ + !*** ./node_modules/component-emitter/index.js ***! + \*************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +/** + * Expose `Emitter`. + */ +if (true) { + module.exports = Emitter; +} +/** + * 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; +}; +/** + * 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; +}; + +/***/ }), + +/***/ "./node_modules/debug/src/browser.js": +/*!*******************************************!*\ + !*** ./node_modules/debug/src/browser.js ***! + \*******************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +/* eslint-env browser */ + +/** + * 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 = __webpack_require__(/*! ./common */ "./node_modules/debug/src/common.js")(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; + } +}; + +/***/ }), + +/***/ "./node_modules/debug/src/common.js": +/*!******************************************!*\ + !*** ./node_modules/debug/src/common.js ***! + \******************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } + +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 _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } + +function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); } + +function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } + +function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } + +/** + * 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 = __webpack_require__(/*! ms */ "./node_modules/ms/index.js"); + 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 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; + + 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() { + return enableOverride === null ? createDebug.enabled(namespace) : enableOverride; + }, + 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.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.substr(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; +} + +module.exports = setup; + +/***/ }), + +/***/ "./node_modules/engine.io-client/lib/globalThis.browser.js": +/*!*****************************************************************!*\ + !*** ./node_modules/engine.io-client/lib/globalThis.browser.js ***! + \*****************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports) { + +module.exports = function () { + if (typeof self !== "undefined") { + return self; + } else if (typeof window !== "undefined") { + return window; + } else { + return Function("return this")(); + } +}(); + +/***/ }), + +/***/ "./node_modules/engine.io-client/lib/index.js": +/*!****************************************************!*\ + !*** ./node_modules/engine.io-client/lib/index.js ***! + \****************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +var Socket = __webpack_require__(/*! ./socket */ "./node_modules/engine.io-client/lib/socket.js"); + +module.exports = function (uri, opts) { + return new Socket(uri, opts); +}; +/** + * Expose deps for legacy compatibility + * and standalone browser access. + */ + + +module.exports.Socket = Socket; +module.exports.protocol = Socket.protocol; // this is an int + +module.exports.Transport = __webpack_require__(/*! ./transport */ "./node_modules/engine.io-client/lib/transport.js"); +module.exports.transports = __webpack_require__(/*! ./transports/index */ "./node_modules/engine.io-client/lib/transports/index.js"); +module.exports.parser = __webpack_require__(/*! engine.io-parser */ "./node_modules/engine.io-parser/lib/index.js"); + +/***/ }), + +/***/ "./node_modules/engine.io-client/lib/socket.js": +/*!*****************************************************!*\ + !*** ./node_modules/engine.io-client/lib/socket.js ***! + \*****************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } + +function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +var transports = __webpack_require__(/*! ./transports/index */ "./node_modules/engine.io-client/lib/transports/index.js"); + +var Emitter = __webpack_require__(/*! component-emitter */ "./node_modules/component-emitter/index.js"); + +var debug = __webpack_require__(/*! debug */ "./node_modules/debug/src/browser.js")("engine.io-client:socket"); + +var parser = __webpack_require__(/*! engine.io-parser */ "./node_modules/engine.io-parser/lib/index.js"); + +var parseuri = __webpack_require__(/*! parseuri */ "./node_modules/parseuri/index.js"); + +var parseqs = __webpack_require__(/*! parseqs */ "./node_modules/parseqs/index.js"); + +var Socket = /*#__PURE__*/function (_Emitter) { + _inherits(Socket, _Emitter); + + var _super = _createSuper(Socket); + + /** + * Socket constructor. + * + * @param {String|Object} uri or options + * @param {Object} options + * @api public + */ + function Socket(uri) { + var _this; + + var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + _classCallCheck(this, Socket); + + _this = _super.call(this); + + if (uri && "object" === _typeof(uri)) { + opts = uri; + uri = null; + } + + if (uri) { + uri = parseuri(uri); + opts.hostname = uri.host; + opts.secure = uri.protocol === "https" || uri.protocol === "wss"; + opts.port = uri.port; + if (uri.query) opts.query = uri.query; + } else if (opts.host) { + opts.hostname = parseuri(opts.host).host; + } + + _this.secure = null != opts.secure ? opts.secure : typeof location !== "undefined" && "https:" === location.protocol; + + if (opts.hostname && !opts.port) { + // if no port is specified manually, use the protocol default + opts.port = _this.secure ? "443" : "80"; + } + + _this.hostname = opts.hostname || (typeof location !== "undefined" ? location.hostname : "localhost"); + _this.port = opts.port || (typeof location !== "undefined" && location.port ? location.port : _this.secure ? 443 : 80); + _this.transports = opts.transports || ["polling", "websocket"]; + _this.readyState = ""; + _this.writeBuffer = []; + _this.prevBufferLen = 0; + _this.opts = _extends({ + path: "/engine.io", + agent: false, + withCredentials: false, + upgrade: true, + jsonp: true, + timestampParam: "t", + rememberUpgrade: false, + rejectUnauthorized: true, + perMessageDeflate: { + threshold: 1024 + }, + transportOptions: {} + }, opts); + _this.opts.path = _this.opts.path.replace(/\/$/, "") + "/"; + + if (typeof _this.opts.query === "string") { + _this.opts.query = parseqs.decode(_this.opts.query); + } // set on handshake + + + _this.id = null; + _this.upgrades = null; + _this.pingInterval = null; + _this.pingTimeout = null; // set on heartbeat + + _this.pingTimeoutTimer = null; + + if (typeof addEventListener === "function") { + addEventListener("beforeunload", function () { + if (_this.transport) { + // silently close the transport + _this.transport.removeAllListeners(); + + _this.transport.close(); + } + }, false); + + if (_this.hostname !== "localhost") { + _this.offlineEventListener = function () { + _this.onClose("transport close"); + }; + + addEventListener("offline", _this.offlineEventListener, false); + } + } + + _this.open(); + + return _this; + } + /** + * Creates transport of the given type. + * + * @param {String} transport name + * @return {Transport} + * @api private + */ + + + _createClass(Socket, [{ + key: "createTransport", + value: function createTransport(name) { + debug('creating transport "%s"', name); + var query = clone(this.opts.query); // append engine.io protocol identifier + + query.EIO = parser.protocol; // transport name + + query.transport = name; // session id if we already have one + + if (this.id) query.sid = this.id; + + var opts = _extends({}, this.opts.transportOptions[name], this.opts, { + query: query, + socket: this, + hostname: this.hostname, + secure: this.secure, + port: this.port + }); + + debug("options: %j", opts); + return new transports[name](opts); + } + /** + * Initializes transport to use and starts probe. + * + * @api private + */ + + }, { + key: "open", + value: function open() { + var transport; + + if (this.opts.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf("websocket") !== -1) { + transport = "websocket"; + } else if (0 === this.transports.length) { + // Emit error on next tick so it can be listened to + var self = this; + setTimeout(function () { + self.emit("error", "No transports available"); + }, 0); + return; + } else { + transport = this.transports[0]; + } + + this.readyState = "opening"; // Retry with the next transport if the transport is disabled (jsonp: false) + + try { + transport = this.createTransport(transport); + } catch (e) { + debug("error while creating transport: %s", e); + this.transports.shift(); + this.open(); + return; + } + + transport.open(); + this.setTransport(transport); + } + /** + * Sets the current transport. Disables the existing one (if any). + * + * @api private + */ + + }, { + key: "setTransport", + value: function setTransport(transport) { + debug("setting transport %s", transport.name); + var self = this; + + if (this.transport) { + debug("clearing existing transport %s", this.transport.name); + this.transport.removeAllListeners(); + } // set up transport + + + this.transport = transport; // set up transport listeners + + transport.on("drain", function () { + self.onDrain(); + }).on("packet", function (packet) { + self.onPacket(packet); + }).on("error", function (e) { + self.onError(e); + }).on("close", function () { + self.onClose("transport close"); + }); + } + /** + * Probes a transport. + * + * @param {String} transport name + * @api private + */ + + }, { + key: "probe", + value: function probe(name) { + debug('probing transport "%s"', name); + var transport = this.createTransport(name, { + probe: 1 + }); + var failed = false; + var self = this; + Socket.priorWebsocketSuccess = false; + + function onTransportOpen() { + if (self.onlyBinaryUpgrades) { + var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary; + failed = failed || upgradeLosesBinary; + } + + if (failed) return; + debug('probe transport "%s" opened', name); + transport.send([{ + type: "ping", + data: "probe" + }]); + transport.once("packet", function (msg) { + if (failed) return; + + if ("pong" === msg.type && "probe" === msg.data) { + debug('probe transport "%s" pong', name); + self.upgrading = true; + self.emit("upgrading", transport); + if (!transport) return; + Socket.priorWebsocketSuccess = "websocket" === transport.name; + debug('pausing current transport "%s"', self.transport.name); + self.transport.pause(function () { + if (failed) return; + if ("closed" === self.readyState) return; + debug("changing transport and sending upgrade packet"); + cleanup(); + self.setTransport(transport); + transport.send([{ + type: "upgrade" + }]); + self.emit("upgrade", transport); + transport = null; + self.upgrading = false; + self.flush(); + }); + } else { + debug('probe transport "%s" failed', name); + var err = new Error("probe error"); + err.transport = transport.name; + self.emit("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 + + + function onerror(err) { + var error = new Error("probe error: " + err); + error.transport = transport.name; + freezeTransport(); + debug('probe transport "%s" failed because of error: %s', name, err); + self.emit("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) { + debug('"%s" works - aborting "%s"', to.name, transport.name); + freezeTransport(); + } + } // Remove all listeners on the transport and on self + + + function cleanup() { + transport.removeListener("open", onTransportOpen); + transport.removeListener("error", onerror); + transport.removeListener("close", onTransportClose); + self.removeListener("close", onclose); + self.removeListener("upgrading", onupgrade); + } + + transport.once("open", onTransportOpen); + transport.once("error", onerror); + transport.once("close", onTransportClose); + this.once("close", onclose); + this.once("upgrading", onupgrade); + transport.open(); + } + /** + * Called when connection is deemed open. + * + * @api public + */ + + }, { + key: "onOpen", + value: function onOpen() { + debug("socket open"); + this.readyState = "open"; + Socket.priorWebsocketSuccess = "websocket" === this.transport.name; + this.emit("open"); + this.flush(); // we check for `readyState` in case an `open` + // listener already closed the socket + + if ("open" === this.readyState && this.opts.upgrade && this.transport.pause) { + debug("starting upgrade probes"); + var i = 0; + var l = this.upgrades.length; + + for (; i < l; i++) { + this.probe(this.upgrades[i]); + } + } + } + /** + * Handles a packet. + * + * @api private + */ + + }, { + key: "onPacket", + value: function onPacket(packet) { + if ("opening" === this.readyState || "open" === this.readyState || "closing" === this.readyState) { + debug('socket receive: type "%s", data "%s"', packet.type, packet.data); + this.emit("packet", packet); // Socket is live - any packet counts + + this.emit("heartbeat"); + + switch (packet.type) { + case "open": + this.onHandshake(JSON.parse(packet.data)); + break; + + case "ping": + this.resetPingTimeout(); + this.sendPacket("pong"); + this.emit("pong"); + break; + + case "error": + var err = new Error("server error"); + err.code = packet.data; + this.onError(err); + break; + + case "message": + this.emit("data", packet.data); + this.emit("message", packet.data); + break; + } + } else { + debug('packet received with socket readyState "%s"', this.readyState); + } + } + /** + * Called upon handshake completion. + * + * @param {Object} handshake obj + * @api private + */ + + }, { + key: "onHandshake", + value: function onHandshake(data) { + this.emit("handshake", data); + this.id = data.sid; + this.transport.query.sid = data.sid; + this.upgrades = this.filterUpgrades(data.upgrades); + this.pingInterval = data.pingInterval; + this.pingTimeout = data.pingTimeout; + this.onOpen(); // In case open handler closes socket + + if ("closed" === this.readyState) return; + this.resetPingTimeout(); + } + /** + * Sets and resets ping timeout timer based on server pings. + * + * @api private + */ + + }, { + key: "resetPingTimeout", + value: function resetPingTimeout() { + var _this2 = this; + + clearTimeout(this.pingTimeoutTimer); + this.pingTimeoutTimer = setTimeout(function () { + _this2.onClose("ping timeout"); + }, this.pingInterval + this.pingTimeout); + + if (this.opts.autoUnref) { + this.pingTimeoutTimer.unref(); + } + } + /** + * Called on `drain` event + * + * @api private + */ + + }, { + key: "onDrain", + value: function onDrain() { + this.writeBuffer.splice(0, this.prevBufferLen); // setting prevBufferLen = 0 is very important + // for example, when upgrading, upgrade packet is sent over, + // and a nonzero prevBufferLen could cause problems on `drain` + + this.prevBufferLen = 0; + + if (0 === this.writeBuffer.length) { + this.emit("drain"); + } else { + this.flush(); + } + } + /** + * Flush write buffers. + * + * @api private + */ + + }, { + key: "flush", + value: function flush() { + if ("closed" !== this.readyState && this.transport.writable && !this.upgrading && this.writeBuffer.length) { + debug("flushing %d packets in socket", this.writeBuffer.length); + this.transport.send(this.writeBuffer); // keep track of current length of writeBuffer + // splice writeBuffer and callbackBuffer on `drain` + + this.prevBufferLen = this.writeBuffer.length; + this.emit("flush"); + } + } + /** + * Sends a message. + * + * @param {String} message. + * @param {Function} callback function. + * @param {Object} options. + * @return {Socket} for chaining. + * @api public + */ + + }, { + key: "write", + value: function write(msg, options, fn) { + this.sendPacket("message", msg, options, fn); + return this; + } + }, { + key: "send", + value: function send(msg, options, fn) { + this.sendPacket("message", msg, options, fn); + return this; + } + /** + * Sends a packet. + * + * @param {String} packet type. + * @param {String} data. + * @param {Object} options. + * @param {Function} callback function. + * @api private + */ + + }, { + key: "sendPacket", + value: function sendPacket(type, data, options, fn) { + if ("function" === typeof data) { + fn = data; + data = undefined; + } + + if ("function" === typeof options) { + fn = options; + options = null; + } + + if ("closing" === this.readyState || "closed" === this.readyState) { + return; + } + + options = options || {}; + options.compress = false !== options.compress; + var packet = { + type: type, + data: data, + options: options + }; + this.emit("packetCreate", packet); + this.writeBuffer.push(packet); + if (fn) this.once("flush", fn); + this.flush(); + } + /** + * Closes the connection. + * + * @api private + */ + + }, { + key: "close", + value: function close() { + var self = this; + + if ("opening" === this.readyState || "open" === this.readyState) { + this.readyState = "closing"; + + if (this.writeBuffer.length) { + this.once("drain", function () { + if (this.upgrading) { + waitForUpgrade(); + } else { + close(); + } + }); + } else if (this.upgrading) { + waitForUpgrade(); + } else { + close(); + } + } + + function close() { + self.onClose("forced close"); + debug("socket closing - telling transport to close"); + self.transport.close(); + } + + function cleanupAndClose() { + self.removeListener("upgrade", cleanupAndClose); + self.removeListener("upgradeError", cleanupAndClose); + close(); + } + + function waitForUpgrade() { + // wait for upgrade to finish since we can't send packets while pausing a transport + self.once("upgrade", cleanupAndClose); + self.once("upgradeError", cleanupAndClose); + } + + return this; + } + /** + * Called upon transport error + * + * @api private + */ + + }, { + key: "onError", + value: function onError(err) { + debug("socket error %j", err); + Socket.priorWebsocketSuccess = false; + this.emit("error", err); + this.onClose("transport error", err); + } + /** + * Called upon transport close. + * + * @api private + */ + + }, { + key: "onClose", + value: function onClose(reason, desc) { + if ("opening" === this.readyState || "open" === this.readyState || "closing" === this.readyState) { + debug('socket close with reason: "%s"', reason); + var self = this; // clear timers + + clearTimeout(this.pingIntervalTimer); + clearTimeout(this.pingTimeoutTimer); // stop event from firing again for transport + + this.transport.removeAllListeners("close"); // ensure transport won't stay open + + this.transport.close(); // ignore further transport communication + + this.transport.removeAllListeners(); + + if (typeof removeEventListener === "function") { + removeEventListener("offline", this.offlineEventListener, false); + } // set ready state + + + this.readyState = "closed"; // clear session id + + this.id = null; // emit close event + + this.emit("close", reason, desc); // clean buffers after, so users can still + // grab the buffers on `close` event + + self.writeBuffer = []; + self.prevBufferLen = 0; + } + } + /** + * Filters upgrades, returning only those matching client transports. + * + * @param {Array} server upgrades + * @api private + * + */ + + }, { + key: "filterUpgrades", + value: function filterUpgrades(upgrades) { + var filteredUpgrades = []; + var i = 0; + var j = upgrades.length; + + for (; i < j; i++) { + if (~this.transports.indexOf(upgrades[i])) filteredUpgrades.push(upgrades[i]); + } + + return filteredUpgrades; + } + }]); + + return Socket; +}(Emitter); + +Socket.priorWebsocketSuccess = false; +/** + * Protocol version. + * + * @api public + */ + +Socket.protocol = parser.protocol; // this is an int + +function clone(obj) { + var o = {}; + + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + o[i] = obj[i]; + } + } + + return o; +} + +module.exports = Socket; + +/***/ }), + +/***/ "./node_modules/engine.io-client/lib/transport.js": +/*!********************************************************!*\ + !*** ./node_modules/engine.io-client/lib/transport.js ***! + \********************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +var parser = __webpack_require__(/*! engine.io-parser */ "./node_modules/engine.io-parser/lib/index.js"); + +var Emitter = __webpack_require__(/*! component-emitter */ "./node_modules/component-emitter/index.js"); + +var Transport = /*#__PURE__*/function (_Emitter) { + _inherits(Transport, _Emitter); + + var _super = _createSuper(Transport); + + /** + * Transport abstract constructor. + * + * @param {Object} options. + * @api private + */ + function Transport(opts) { + var _this; + + _classCallCheck(this, Transport); + + _this = _super.call(this); + _this.opts = opts; + _this.query = opts.query; + _this.readyState = ""; + _this.socket = opts.socket; + return _this; + } + /** + * Emits an error. + * + * @param {String} str + * @return {Transport} for chaining + * @api public + */ + + + _createClass(Transport, [{ + key: "onError", + value: function onError(msg, desc) { + var err = new Error(msg); + err.type = "TransportError"; + err.description = desc; + this.emit("error", err); + return this; + } + /** + * Opens the transport. + * + * @api public + */ + + }, { + key: "open", + value: function open() { + if ("closed" === this.readyState || "" === this.readyState) { + this.readyState = "opening"; + this.doOpen(); + } + + return this; + } + /** + * Closes the transport. + * + * @api private + */ + + }, { + key: "close", + value: function close() { + if ("opening" === this.readyState || "open" === this.readyState) { + this.doClose(); + this.onClose(); + } + + return this; + } + /** + * Sends multiple packets. + * + * @param {Array} packets + * @api private + */ + + }, { + key: "send", + value: function send(packets) { + if ("open" === this.readyState) { + this.write(packets); + } else { + throw new Error("Transport not open"); + } + } + /** + * Called upon open + * + * @api private + */ + + }, { + key: "onOpen", + value: function onOpen() { + this.readyState = "open"; + this.writable = true; + this.emit("open"); + } + /** + * Called with data. + * + * @param {String} data + * @api private + */ + + }, { + key: "onData", + value: function onData(data) { + var packet = parser.decodePacket(data, this.socket.binaryType); + this.onPacket(packet); + } + /** + * Called with a decoded packet. + */ + + }, { + key: "onPacket", + value: function onPacket(packet) { + this.emit("packet", packet); + } + /** + * Called upon close. + * + * @api private + */ + + }, { + key: "onClose", + value: function onClose() { + this.readyState = "closed"; + this.emit("close"); + } + }]); + + return Transport; +}(Emitter); + +module.exports = Transport; + +/***/ }), + +/***/ "./node_modules/engine.io-client/lib/transports/index.js": +/*!***************************************************************!*\ + !*** ./node_modules/engine.io-client/lib/transports/index.js ***! + \***************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +var XMLHttpRequest = __webpack_require__(/*! ../../contrib/xmlhttprequest-ssl/XMLHttpRequest */ "./node_modules/engine.io-client/lib/xmlhttprequest.js"); + +var XHR = __webpack_require__(/*! ./polling-xhr */ "./node_modules/engine.io-client/lib/transports/polling-xhr.js"); + +var JSONP = __webpack_require__(/*! ./polling-jsonp */ "./node_modules/engine.io-client/lib/transports/polling-jsonp.js"); + +var websocket = __webpack_require__(/*! ./websocket */ "./node_modules/engine.io-client/lib/transports/websocket.js"); + +exports.polling = polling; +exports.websocket = websocket; +/** + * Polling transport polymorphic constructor. + * Decides on xhr vs jsonp based on feature detection. + * + * @api private + */ + +function polling(opts) { + var xhr; + var xd = false; + var xs = false; + var jsonp = false !== opts.jsonp; + + 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; + } + + xd = opts.hostname !== location.hostname || port !== opts.port; + xs = opts.secure !== isSSL; + } + + opts.xdomain = xd; + opts.xscheme = xs; + xhr = new XMLHttpRequest(opts); + + if ("open" in xhr && !opts.forceJSONP) { + return new XHR(opts); + } else { + if (!jsonp) throw new Error("JSONP disabled"); + return new JSONP(opts); + } +} + +/***/ }), + +/***/ "./node_modules/engine.io-client/lib/transports/polling-jsonp.js": +/*!***********************************************************************!*\ + !*** ./node_modules/engine.io-client/lib/transports/polling-jsonp.js ***! + \***********************************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); } + +function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } + +function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } + +function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } + +function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } + +var Polling = __webpack_require__(/*! ./polling */ "./node_modules/engine.io-client/lib/transports/polling.js"); + +var globalThis = __webpack_require__(/*! ../globalThis */ "./node_modules/engine.io-client/lib/globalThis.browser.js"); + +var rNewline = /\n/g; +var rEscapedNewline = /\\n/g; +/** + * Global JSONP callbacks. + */ + +var callbacks; + +var JSONPPolling = /*#__PURE__*/function (_Polling) { + _inherits(JSONPPolling, _Polling); + + var _super = _createSuper(JSONPPolling); + + /** + * JSONP Polling constructor. + * + * @param {Object} opts. + * @api public + */ + function JSONPPolling(opts) { + var _this; + + _classCallCheck(this, JSONPPolling); + + _this = _super.call(this, opts); + _this.query = _this.query || {}; // define global callbacks array if not present + // we do this here (lazily) to avoid unneeded global pollution + + if (!callbacks) { + // we need to consider multiple engines in the same page + callbacks = globalThis.___eio = globalThis.___eio || []; + } // callback identifier + + + _this.index = callbacks.length; // add callback to jsonp global + + var self = _assertThisInitialized(_this); + + callbacks.push(function (msg) { + self.onData(msg); + }); // append to query string + + _this.query.j = _this.index; + return _this; + } + /** + * JSONP only supports binary as base64 encoded strings + */ + + + _createClass(JSONPPolling, [{ + key: "doClose", + + /** + * Closes the socket. + * + * @api private + */ + value: function doClose() { + if (this.script) { + // prevent spurious errors from being emitted when the window is unloaded + this.script.onerror = function () {}; + + this.script.parentNode.removeChild(this.script); + this.script = null; + } + + if (this.form) { + this.form.parentNode.removeChild(this.form); + this.form = null; + this.iframe = null; + } + + _get(_getPrototypeOf(JSONPPolling.prototype), "doClose", this).call(this); + } + /** + * Starts a poll cycle. + * + * @api private + */ + + }, { + key: "doPoll", + value: function doPoll() { + var self = this; + var script = document.createElement("script"); + + if (this.script) { + this.script.parentNode.removeChild(this.script); + this.script = null; + } + + script.async = true; + script.src = this.uri(); + + script.onerror = function (e) { + self.onError("jsonp poll error", e); + }; + + var insertAt = document.getElementsByTagName("script")[0]; + + if (insertAt) { + insertAt.parentNode.insertBefore(script, insertAt); + } else { + (document.head || document.body).appendChild(script); + } + + this.script = script; + var isUAgecko = "undefined" !== typeof navigator && /gecko/i.test(navigator.userAgent); + + if (isUAgecko) { + setTimeout(function () { + var iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + document.body.removeChild(iframe); + }, 100); + } + } + /** + * Writes with a hidden iframe. + * + * @param {String} data to send + * @param {Function} called upon flush. + * @api private + */ + + }, { + key: "doWrite", + value: function doWrite(data, fn) { + var self = this; + var iframe; + + if (!this.form) { + var form = document.createElement("form"); + var area = document.createElement("textarea"); + var id = this.iframeId = "eio_iframe_" + this.index; + form.className = "socketio"; + form.style.position = "absolute"; + form.style.top = "-1000px"; + form.style.left = "-1000px"; + form.target = id; + form.method = "POST"; + form.setAttribute("accept-charset", "utf-8"); + area.name = "d"; + form.appendChild(area); + document.body.appendChild(form); + this.form = form; + this.area = area; + } + + this.form.action = this.uri(); + + function complete() { + initIframe(); + fn(); + } + + function initIframe() { + if (self.iframe) { + try { + self.form.removeChild(self.iframe); + } catch (e) { + self.onError("jsonp polling iframe removal error", e); + } + } + + try { + // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) + var html = '