diff --git a/lnbits/app.py b/lnbits/app.py index e2858a8ea..f1f55b833 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -48,7 +48,10 @@ from lnbits.core.tasks import ( process_next_audit_entry, refresh_extension_cache, ) -from lnbits.core.wasm_ext.routes.register import register_wasm_extension +from lnbits.core.wasm_ext.routes.register import ( + register_wasm_extension, + unregister_wasm_extension, +) from lnbits.core.wasm_ext.wasm.events import dispatch_wasm_invoice_paid from lnbits.core.wasm_ext.wasm.loader import ( is_wasm_extension_id, @@ -177,6 +180,7 @@ def create_app() -> FastAPI: # Allow registering new extensions routes without direct access to the `app` object core_app_extra.register_new_ext_routes = register_new_ext_routes(app) core_app_extra.register_new_wasm_ext_routes = register_new_wasm_ext_routes(app) + core_app_extra.unregister_wasm_ext_routes = unregister_wasm_ext_routes(app) core_app_extra.register_new_ratelimiter = register_new_ratelimiter(app) # register static files @@ -430,6 +434,13 @@ def register_new_wasm_ext_routes(app: FastAPI) -> Callable: return register_new_wasm_ext_routes_fn +def unregister_wasm_ext_routes(app: FastAPI) -> Callable: + def unregister_wasm_ext_routes_fn(ext_id: str): + unregister_wasm_extension(app, ext_id) + + return unregister_wasm_ext_routes_fn + + def register_new_ratelimiter(app: FastAPI) -> Callable: def register_new_ratelimiter_fn(): limiter = Limiter( diff --git a/lnbits/core/models/misc.py b/lnbits/core/models/misc.py index 9306ad03f..824bb2d01 100644 --- a/lnbits/core/models/misc.py +++ b/lnbits/core/models/misc.py @@ -14,6 +14,7 @@ def _do_nothing(*_): class CoreAppExtra: register_new_ext_routes: Callable = _do_nothing register_new_wasm_ext_routes: Callable = _do_nothing + unregister_wasm_ext_routes: Callable = _do_nothing register_new_ratelimiter: Callable def __init__(self) -> None: @@ -38,6 +39,9 @@ class WasmExtensionRegistry: def get(self, ext_id: str) -> Any | None: return self._extensions.get(ext_id) + def unregister(self, ext_id: str) -> None: + self._extensions.pop(ext_id, None) + def list(self) -> list[Any]: return list(self._extensions.values()) diff --git a/lnbits/core/services/extensions.py b/lnbits/core/services/extensions.py index 9c1be8cd3..a68fffcc9 100644 --- a/lnbits/core/services/extensions.py +++ b/lnbits/core/services/extensions.py @@ -639,6 +639,7 @@ def _now() -> datetime: async def uninstall_extension(ext_id: str): await stop_extension_background_work(ext_id) + core_app_extra.unregister_wasm_ext_routes(ext_id) settings.deactivate_extension_paths(ext_id) diff --git a/lnbits/core/wasm_ext/api/websockets.py b/lnbits/core/wasm_ext/api/websockets.py index fd839fe19..a53c9943c 100644 --- a/lnbits/core/wasm_ext/api/websockets.py +++ b/lnbits/core/wasm_ext/api/websockets.py @@ -112,7 +112,7 @@ class WasmExtensionWebsocketHub: max_messages_per_second=max_messages_per_second, ) for conn in self.get_connections(extension_id, item_id): - await conn.websocket.send_text(data) + await self._send_to_connection(conn, data) def _check_publish_rate( self, @@ -160,7 +160,17 @@ class WasmExtensionWebsocketHub: for active_conn in self.get_connections(conn.extension_id, conn.item_id): if active_conn.websocket == conn.websocket: continue - await active_conn.websocket.send_text(data) + await self._send_to_connection(active_conn, data) + + async def _send_to_connection( + self, + conn: WasmExtensionWebsocketConnection, + data: str, + ) -> None: + try: + await conn.websocket.send_text(data) + except (RuntimeError, WebSocketDisconnect): + self.disconnect(conn) wasm_extension_websocket_hub = WasmExtensionWebsocketHub() diff --git a/lnbits/core/wasm_ext/routes/__init__.py b/lnbits/core/wasm_ext/routes/__init__.py index a1f650938..c2b8497dd 100644 --- a/lnbits/core/wasm_ext/routes/__init__.py +++ b/lnbits/core/wasm_ext/routes/__init__.py @@ -1,3 +1,3 @@ -from .register import register_wasm_extension +from .register import register_wasm_extension, unregister_wasm_extension -__all__ = ["register_wasm_extension"] +__all__ = ["register_wasm_extension", "unregister_wasm_extension"] diff --git a/lnbits/core/wasm_ext/routes/api.py b/lnbits/core/wasm_ext/routes/api.py index 11ca27d2e..df695e9ec 100644 --- a/lnbits/core/wasm_ext/routes/api.py +++ b/lnbits/core/wasm_ext/routes/api.py @@ -16,6 +16,9 @@ from lnbits.settings import settings from ..wasm.config import WasmAPIRouteConfig from ..wasm.invoke import invoke_wasm_extension_export from ..wasm.loader import WasmExtension +from .open_api import wasm_extension_api_openapi_metadata, wasm_extension_api_tag + +_WASM_EXTENSION_API_METHODS = {"GET", "POST", "PUT", "PATCH", "DELETE"} class WasmRequestBodyTooLargeError(ValueError): @@ -28,24 +31,54 @@ class WasmRoutePayload: request_bytes: int | None +@dataclass(frozen=True) +class WasmAPIRouteRegistration: + route_config: WasmAPIRouteConfig + method: str + route_path: str + export_name: str + path_params: dict[str, str] + auth: str + route_name: str + + def register_wasm_extension_api_routes(app: FastAPI, extension: WasmExtension) -> None: - for route_config in extension.config.api_routes: - _add_wasm_extension_api_route(app, extension, route_config) + route_registrations = [ + _wasm_extension_api_route_registration(extension, route_config) + for route_config in extension.config.api_routes + ] + + openapi_schema_changed = _remove_wasm_extension_api_routes(app, extension.id) + for route_registration in route_registrations: + if _add_wasm_extension_api_route(app, extension, route_registration): + openapi_schema_changed = True + if openapi_schema_changed: + app.openapi_schema = None + + +def unregister_wasm_extension_api_routes(app: FastAPI, ext_id: str) -> bool: + openapi_schema_changed = _remove_wasm_extension_api_routes(app, ext_id) + if openapi_schema_changed: + app.openapi_schema = None + return openapi_schema_changed def _add_wasm_extension_api_route( app: FastAPI, extension: WasmExtension, - route_config: WasmAPIRouteConfig, -) -> None: - method = _wasm_extension_api_method(extension, route_config.method) - route_path = _wasm_extension_api_path(extension, route_config.path) - export_name = _wasm_extension_api_export(extension, route_config.export) - path_params = route_config.path_params - auth = _wasm_extension_route_auth(extension, route_config.auth) + route_registration: WasmAPIRouteRegistration, +) -> bool: + method = route_registration.method + route_path = route_registration.route_path + export_name = route_registration.export_name + path_params = route_registration.path_params + auth = route_registration.auth + route_name = route_registration.route_name + route_config = route_registration.route_config + openapi = wasm_extension_api_openapi_metadata(extension, route_config, method) - if _has_route(app, route_path, method): - return + if not _prepare_wasm_extension_api_route(app, route_path, method, route_name): + return False async def invoke_wasm_api_request( request: Request, @@ -102,9 +135,15 @@ def _add_wasm_extension_api_route( else invoke_private_wasm_extension_export ), methods=[method], - name=f"{extension.id}:{method}:{route_path}", - include_in_schema=False, + name=route_name, + tags=[wasm_extension_api_tag(extension)], + summary=openapi.summary, + description=openapi.description, + operation_id=openapi.operation_id, + openapi_extra=openapi.openapi_extra, + include_in_schema=True, ) + return True async def _read_api_payload( @@ -247,7 +286,7 @@ def _wasm_extension_api_method(extension: WasmExtension, method: Any) -> str: if not isinstance(method, str): raise ValueError(f"Invalid API method for WASM extension '{extension.id}'.") method = method.upper() - if method not in {"GET", "POST", "PUT", "PATCH", "DELETE"}: + if method not in _WASM_EXTENSION_API_METHODS: raise ValueError(f"Unsupported API method for WASM extension '{extension.id}'.") return method @@ -276,6 +315,77 @@ def _has_route(app: FastAPI, route_path: str, method: str) -> bool: return False +def _wasm_extension_api_route_registration( + extension: WasmExtension, + route_config: WasmAPIRouteConfig, +) -> WasmAPIRouteRegistration: + method = _wasm_extension_api_method(extension, route_config.method) + route_path = _wasm_extension_api_path(extension, route_config.path) + return WasmAPIRouteRegistration( + route_config=route_config, + method=method, + route_path=route_path, + export_name=_wasm_extension_api_export(extension, route_config.export), + path_params=route_config.path_params, + auth=_wasm_extension_route_auth(extension, route_config.auth), + route_name=_wasm_extension_api_route_name(extension.id, method, route_path), + ) + + +def _remove_wasm_extension_api_routes(app: FastAPI, ext_id: str) -> bool: + removed = False + for route in list(app.router.routes): + if not _is_wasm_extension_api_route(route, ext_id): + continue + app.router.routes.remove(route) + removed = True + return removed + + +def _is_wasm_extension_api_route(route: Any, ext_id: str) -> bool: + route_name = getattr(route, "name", None) + if not isinstance(route_name, str) or not route_name.startswith(f"{ext_id}:"): + return False + route_name_parts = route_name.split(":", 2) + if len(route_name_parts) != 3: + return False + _, route_method, named_route_path = route_name_parts + if route_method not in _WASM_EXTENSION_API_METHODS: + return False + + route_path = getattr(route, "path", None) + if not isinstance(route_path, str): + return False + + route_prefix = f"/api/v1/ext/{ext_id}" + if route_path != route_prefix and not route_path.startswith(f"{route_prefix}/"): + return False + return named_route_path == route_path + + +def _prepare_wasm_extension_api_route( + app: FastAPI, + route_path: str, + method: str, + route_name: str, +) -> bool: + for route in list(app.router.routes): + if getattr(route, "path", None) != route_path: + continue + methods = getattr(route, "methods", set()) or set() + if method not in methods: + continue + if getattr(route, "name", None) != route_name: + return False + app.router.routes.remove(route) + return True + return True + + +def _wasm_extension_api_route_name(ext_id: str, method: str, route_path: str) -> str: + return f"{ext_id}:{method}:{route_path}" + + def _snake_to_camel(value: str) -> str: head, *tail = value.split("_") return head + "".join(part.capitalize() for part in tail) diff --git a/lnbits/core/wasm_ext/routes/open_api.py b/lnbits/core/wasm_ext/routes/open_api.py new file mode 100644 index 000000000..d3c0da8af --- /dev/null +++ b/lnbits/core/wasm_ext/routes/open_api.py @@ -0,0 +1,356 @@ +from __future__ import annotations + +import json +import re +from copy import deepcopy +from dataclasses import dataclass +from typing import Any + +from loguru import logger + +from ..wasm.config import WasmAPIRouteConfig +from ..wasm.loader import WasmExtension + + +@dataclass(frozen=True) +class WasmOpenAPIMetadata: + summary: str + description: str | None + operation_id: str + openapi_extra: dict[str, Any] | None + + +_MISSING_OPENAPI_EXAMPLE = object() + + +def wasm_extension_api_tag(extension: WasmExtension) -> str: + return extension.name.strip() or extension.id + + +def wasm_extension_api_openapi_metadata( + extension: WasmExtension, + route_config: WasmAPIRouteConfig, + method: str, +) -> WasmOpenAPIMetadata: + operation = _load_wasm_extension_openapi_operation( + extension, + route_config, + ) + summary = _openapi_string(operation.pop("summary", None)) or ( + f"{method} {route_config.path}" + ) + description = _openapi_string(operation.pop("description", None)) + operation_id = ( + _openapi_string(operation.pop("operationId", None)) + or _openapi_string(operation.pop("operation_id", None)) + or _wasm_extension_default_operation_id(extension, route_config, method) + ) + operation.pop("tags", None) + _add_wasm_openapi_success_examples(operation) + return WasmOpenAPIMetadata( + summary=summary, + description=description, + operation_id=operation_id, + openapi_extra=operation or None, + ) + + +def _load_wasm_extension_openapi_operation( + extension: WasmExtension, + route_config: WasmAPIRouteConfig, +) -> dict[str, Any]: + try: + openapi_refs = _wasm_extension_openapi_refs(extension, route_config) + except Exception as exc: + logger.warning( + f"Ignoring OpenAPI metadata for WASM extension '{extension.id}' " + f"route '{route_config.path}': {exc}" + ) + return {} + if not openapi_refs: + return {} + + errors: list[Exception] = [] + for openapi_ref in openapi_refs: + try: + document_path, pointer = _wasm_openapi_ref_parts(openapi_ref) + document = _load_wasm_openapi_document(extension, document_path) + operation = _resolve_json_pointer(document, pointer) + if not isinstance(operation, dict): + raise TypeError("OpenAPI route fragment must resolve to an object.") + return _inline_wasm_openapi_refs(deepcopy(operation), document) + except Exception as exc: + errors.append(exc) + + logger.warning( + f"Ignoring OpenAPI metadata for WASM extension '{extension.id}' " + f"route '{route_config.path}': {errors[-1]}" + ) + return {} + + +def _wasm_extension_openapi_refs( + extension: WasmExtension, + route_config: WasmAPIRouteConfig, +) -> list[str]: + if route_config.openapi: + return [ + _wasm_openapi_resolved_ref( + extension.config.openapi, + route_config.openapi, + ) + ] + + document_path = extension.config.openapi + if not document_path: + return [] + + document_path = _wasm_openapi_document_path(document_path) + route_keys = [route_config.export, _wasm_openapi_route_key(route_config.export)] + return [ + f"{document_path}#/routes/{_json_pointer_token(route_key)}" + for route_key in dict.fromkeys(route_keys) + ] + + +def _wasm_openapi_resolved_ref( + base_ref: str | None, + route_ref: str, +) -> str: + if not route_ref.startswith("#"): + return route_ref + if not base_ref: + raise ValueError("OpenAPI metadata reference must include a JSON file path.") + return f"{_wasm_openapi_document_path(base_ref)}{route_ref}" + + +def _wasm_openapi_document_path(openapi_ref: str) -> str: + document_path, _, _ = openapi_ref.partition("#") + if not document_path: + raise ValueError("OpenAPI metadata reference must include a JSON file path.") + return document_path + + +def _wasm_openapi_route_key(export_name: str) -> str: + return re.sub(r"[^A-Za-z0-9]+", "_", export_name).strip("_").lower() + + +def _json_pointer_token(value: str) -> str: + return value.replace("~", "~0").replace("/", "~1") + + +def _wasm_openapi_ref_parts(openapi_ref: str) -> tuple[str, str]: + document_path, _, pointer = openapi_ref.partition("#") + if not document_path: + raise ValueError("OpenAPI metadata reference must include a JSON file path.") + return document_path, pointer + + +def _load_wasm_openapi_document( + extension: WasmExtension, + document_path: str, +) -> dict[str, Any]: + if "://" in document_path or document_path.startswith(("/", "\\")): + raise ValueError("OpenAPI metadata reference must be a local relative path.") + if not document_path.lower().endswith(".json"): + raise ValueError("OpenAPI metadata reference must point to a JSON file.") + + extension_root = extension.root_path.resolve() + path = (extension_root / document_path).resolve() + if not path.is_relative_to(extension_root): + raise ValueError("OpenAPI metadata reference escapes the extension root.") + if not path.is_file(): + raise FileNotFoundError(f"OpenAPI metadata file not found: {document_path}") + + with path.open("r", encoding="utf-8") as openapi_file: + document = json.load(openapi_file) + if not isinstance(document, dict): + raise TypeError("OpenAPI metadata file must contain a JSON object.") + return document + + +def _resolve_json_pointer(document: Any, pointer: str) -> Any: + if not pointer: + return document + if not pointer.startswith("/"): + raise ValueError("OpenAPI metadata reference must use a JSON pointer.") + + value = document + for raw_token in pointer[1:].split("/"): + token = raw_token.replace("~1", "/").replace("~0", "~") + if isinstance(value, dict): + value = value[token] + elif isinstance(value, list): + value = value[int(token)] + else: + raise KeyError(token) + return value + + +def _inline_wasm_openapi_refs( + value: Any, + document: dict[str, Any], + seen_refs: tuple[str, ...] = (), +) -> Any: + if isinstance(value, list): + return [_inline_wasm_openapi_refs(item, document, seen_refs) for item in value] + if not isinstance(value, dict): + return value + + ref = value.get("$ref") + if isinstance(ref, str) and ref.startswith("#/") and ref not in seen_refs: + try: + resolved = _inline_wasm_openapi_refs( + deepcopy(_resolve_json_pointer(document, ref[1:])), + document, + (*seen_refs, ref), + ) + except Exception: + resolved = None + + if resolved is not None: + overrides = { + key: _inline_wasm_openapi_refs(item, document, seen_refs) + for key, item in value.items() + if key != "$ref" + } + if isinstance(resolved, dict): + return {**resolved, **overrides} + if not overrides: + return resolved + + return { + key: _inline_wasm_openapi_refs(item, document, seen_refs) + for key, item in value.items() + } + + +def _add_wasm_openapi_success_examples(operation: dict[str, Any]) -> None: + responses = operation.get("responses") + if not isinstance(responses, dict): + return + + for response in responses.values(): + if not isinstance(response, dict): + continue + content = response.get("content") + if not isinstance(content, dict): + continue + json_content = content.get("application/json") + if not isinstance(json_content, dict): + continue + if "example" in json_content or "examples" in json_content: + continue + + schema = json_content.get("schema") + example = _wasm_openapi_success_example(schema) + if example is not None: + json_content["example"] = example + + +def _wasm_openapi_success_example(schema: Any) -> Any | None: + if not isinstance(schema, dict): + return None + + for keyword in ("oneOf", "anyOf"): + variants = schema.get(keyword) + if not isinstance(variants, list): + continue + for variant in variants: + if _wasm_openapi_schema_has_ok_value(variant, True): + return _wasm_openapi_schema_example(variant) + return None + + +def _wasm_openapi_schema_has_ok_value(schema: Any, ok_value: bool) -> bool: + if not isinstance(schema, dict): + return False + properties = schema.get("properties") + if isinstance(properties, dict): + ok_schema = properties.get("ok") + if isinstance(ok_schema, dict): + enum = ok_schema.get("enum") + return isinstance(enum, list) and ok_value in enum + all_of = schema.get("allOf") + return isinstance(all_of, list) and any( + _wasm_openapi_schema_has_ok_value(item, ok_value) for item in all_of + ) + + +def _wasm_openapi_schema_example(schema: Any) -> Any: + if not isinstance(schema, dict): + return None + + explicit_example = _wasm_openapi_explicit_schema_example(schema) + if explicit_example is not _MISSING_OPENAPI_EXAMPLE: + return explicit_example + + composed_example = _wasm_openapi_composed_schema_example(schema) + if composed_example is not _MISSING_OPENAPI_EXAMPLE: + return composed_example + + return _wasm_openapi_type_schema_example(schema) + + +def _wasm_openapi_explicit_schema_example(schema: dict[str, Any]) -> Any: + if "example" in schema: + return schema["example"] + enum = schema.get("enum") + if isinstance(enum, list) and enum: + return enum[0] + return _MISSING_OPENAPI_EXAMPLE + + +def _wasm_openapi_composed_schema_example(schema: dict[str, Any]) -> Any: + all_of = schema.get("allOf") + if isinstance(all_of, list): + example: dict[str, Any] = {} + for item in all_of: + item_example = _wasm_openapi_schema_example(item) + if isinstance(item_example, dict): + example.update(item_example) + return example + + for keyword in ("oneOf", "anyOf"): + variants = schema.get(keyword) + if isinstance(variants, list) and variants: + return _wasm_openapi_schema_example(variants[0]) + return _MISSING_OPENAPI_EXAMPLE + + +def _wasm_openapi_type_schema_example(schema: dict[str, Any]) -> Any: + schema_type = schema.get("type") + if schema_type == "object" or isinstance(schema.get("properties"), dict): + properties = schema.get("properties") + if not isinstance(properties, dict): + return {} + return { + name: _wasm_openapi_schema_example(property_schema) + for name, property_schema in properties.items() + } + if schema_type == "array": + return [_wasm_openapi_schema_example(schema.get("items"))] + if schema_type == "integer": + return 0 + if schema_type == "number": + return 0 + if schema_type == "boolean": + return True + return "string" + + +def _openapi_string(value: Any) -> str | None: + if not isinstance(value, str): + return None + value = value.strip() + return value or None + + +def _wasm_extension_default_operation_id( + extension: WasmExtension, + route_config: WasmAPIRouteConfig, + method: str, +) -> str: + value = f"{extension.id}_{method}_{route_config.path}" + value = re.sub(r"[^A-Za-z0-9]+", "_", value).strip("_").lower() + return value or f"{extension.id}_{method.lower()}" diff --git a/lnbits/core/wasm_ext/routes/register.py b/lnbits/core/wasm_ext/routes/register.py index 581e3d5fb..ca7a3ab19 100644 --- a/lnbits/core/wasm_ext/routes/register.py +++ b/lnbits/core/wasm_ext/routes/register.py @@ -8,7 +8,10 @@ from lnbits.settings import settings from ..wasm.component import warm_wasm_extension from ..wasm.loader import WasmExtension, load_wasm_extension -from .api import register_wasm_extension_api_routes +from .api import ( + register_wasm_extension_api_routes, + unregister_wasm_extension_api_routes, +) from .assets import mount_wasm_extension_static from .ui import register_wasm_extension_ui_routes @@ -30,3 +33,10 @@ def register_wasm_extension(app: FastAPI, ext_id: str) -> WasmExtension: f"({loaded.module_path.stat().st_size} bytes)." ) return loaded + + +def unregister_wasm_extension(app: FastAPI, ext_id: str) -> None: + routes_removed = unregister_wasm_extension_api_routes(app, ext_id) + core_app_extra.wasm_extension_registry.unregister(ext_id) + if routes_removed: + logger.info(f"Unloaded WASM extension API routes for '{ext_id}'.") diff --git a/lnbits/core/wasm_ext/wasm/config.py b/lnbits/core/wasm_ext/wasm/config.py index 60c3ce6f0..947b3a513 100644 --- a/lnbits/core/wasm_ext/wasm/config.py +++ b/lnbits/core/wasm_ext/wasm/config.py @@ -62,6 +62,7 @@ class WasmAPIRouteConfig(_StrictWasmModel): auth: Literal["public", "user"] path_params: dict[str, StrictStr] = Field(default_factory=dict) owner_context: WasmRouteOwnerContext | None = Field(None, alias="ownerContext") + openapi: StrictStr | None = None class WasmEventsConfig(_StrictWasmModel): @@ -83,6 +84,7 @@ class WasmExtensionConfig(_StrictWasmModel): ) ui: WasmUIConfig | None = None sdk: WasmSDKConfig | None = None + openapi: StrictStr | None = None ui_routes: list[WasmUIRouteConfig] = Field(default_factory=list) api_routes: list[WasmAPIRouteConfig] = Field(default_factory=list) permissions: list[ExtensionPermission] = Field(default_factory=list) diff --git a/lnbits/static/bundle-components.min.js b/lnbits/static/bundle-components.min.js index c8a22afc1..39bc46ebe 100644 --- a/lnbits/static/bundle-components.min.js +++ b/lnbits/static/bundle-components.min.js @@ -1 +1 @@ -window.PageError={template:"#page-error"},window.PageHome={template:"#page-home",data:()=>({lnurl:"",authAction:"login",authMethod:"username-password",usr:"",username:"",reset_key:"",email:"",password:"",passwordRepeat:"",invitationCode:"",walletName:"",signup:!1}),computed:{showClaimLnurl(){return""!==this.lnurl&&this.g.settings.allowRegister&&this.g.settings.authMethods.includes("user-id-only")},formatDescription(){return LNbits.utils.convertMarkdown(this.g.settings.siteDescription)},isAccessTokenExpired(){return this.$q.cookies.get("is_access_token_expired")}},methods:{showLogin(e){this.authAction="login",this.authMethod=e},showRegister(e){this.user="",this.username=null,this.password=null,this.passwordRepeat=null,this.invitationCode=null,this.authAction="register",this.authMethod=e},async register(){try{await LNbits.api.register(this.username,this.email,this.password,this.passwordRepeat,this.invitationCode),this.refreshAuthUser()}catch(e){LNbits.utils.notifyApiError(e)}},async reset(){try{await LNbits.api.reset(this.reset_key,this.password,this.passwordRepeat),this.refreshAuthUser()}catch(e){LNbits.utils.notifyApiError(e)}},async login(){try{await LNbits.api.login(this.username,this.password),this.refreshAuthUser()}catch(e){LNbits.utils.notifyApiError(e)}},async loginUsr(){try{await LNbits.api.loginUsr(this.usr),this.refreshAuthUser()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}},async refreshAuthUser(){try{const e=await LNbits.api.getAuthUser();this.g.user=LNbits.map.user(e.data),this.g.isPublicPage=!1,this.$router.push(`/wallet/${this.g.user.wallets[0].id}`)}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}},createWallet(){LNbits.api.createAccount(this.walletName).then(e=>{this.$router.push(`/wallet/${e.data.id}`)})},processing(){Quasar.Notify.create({timeout:0,message:"Processing...",icon:null})}},created(){if(this.g.isUserAuthorized)return this.refreshAuthUser();const e=new URLSearchParams(window.location.search);this.reset_key=e.get("reset_key"),this.reset_key&&(this.authAction="reset"),e.has("lightning")&&(this.lnurl=e.get("lightning"))}},window.PageExtensionBuilder={template:"#page-extension-builder",data:()=>({step:1,previewStepNames:{2:"settings",3:"owner_data",4:"client_data",5:"public_page"},extensionDataCleanString:"",extensionData:{id:"",name:"",stub_version:"",short_description:"",description:"",public_page:{has_public_page:!0,owner_data_fields:{name:"",description:""},client_data_fields:{public_inputs:[]},action_fields:{generate_action:!0,generate_payment_logic:!1,wallet_id:"",currency:"",amount:"",paid_flag:""}},preview_action:{is_preview_mode:!1,is_settings_preview:!1,is_owner_data_preview:!1,is_client_data_preview:!1,is_public_page_preview:!1},settings_data:{name:"Settings",enabled:!0,type:"user",fields:[]},owner_data:{name:"OwnerData",fields:[]},client_data:{enabled:!0,name:"ClientData",fields:[]}},sampleField:{name:"name",type:"str",label:"Name",hint:"",optional:!0,editable:!0,searchable:!0,sortable:!0},settingsTypes:[{label:"User Settings",value:"user"},{label:"Admin Settings",value:"admin"}],amountSource:[{label:"Client Data",value:"client_data"},{label:"Owner Data",value:"owner_data"}],extensionStubVersions:[]}),watch:{"extensionData.public_page.action_fields.amount_source":function(e,t){t&&e!==t&&(this.extensionData.public_page.action_fields.amount="")}},computed:{paymentActionAmountFields(){const e=this.extensionData.public_page.action_fields.amount_source;return e?"owner_data"===e?[""].concat(this.extensionData.owner_data.fields.filter(e=>"int"===e.type||"float"===e.type).map(e=>e.name)):"client_data"===e?[""].concat(this.extensionData.client_data.fields.filter(e=>"int"===e.type||"float"===e.type).map(e=>e.name)):void 0:[""]}},methods:{saveState(){this.$q.localStorage.set("lnbits.extension.builder.data",JSON.stringify(this.extensionData)),this.$q.localStorage.set("lnbits.extension.builder.step",this.step)},nextStep(){this.saveState(),this.$refs.stepper.next(),this.refreshPreview()},previousStep(){this.saveState(),this.$refs.stepper.previous(),this.refreshPreview()},onStepChange(){this.saveState(),this.refreshPreview()},clearAllData(){LNbits.utils.confirmDialog("Are you sure you want to clear all data? This action cannot be undone.").onOk(()=>{this.extensionData=JSON.parse(this.extensionDataCleanString),this.$q.localStorage.remove("lnbits.extension.builder.data"),this.$refs.stepper.set(1)})},exportJsonData(){!0!==Quasar.exportFile(`${this.extensionData.id||"data-export"}.json`,JSON.stringify(this.extensionData,null,2),"text/json")?Quasar.Notify.create({message:"Browser denied file download...",color:"negative",icon:null}):Quasar.Notify.create({message:"File downloaded!",color:"positive",icon:"file_download"})},onJsonDataInput(e){const t=e.target.files[0],s=new FileReader;s.onload=e=>{this.extensionData={...this.extensionData,...JSON.parse(e.target.result)},this.$refs.extensionDataInput.value=null,Quasar.Notify.create({message:"File loaded!",color:"positive",icon:"file_upload"})},s.readAsText(t)},async buildExtension(){try{const e={responseType:"blob"},t=await LNbits.api.request("POST","/api/v1/extension/builder/zip",null,this.extensionData,e),s=window.URL.createObjectURL(new Blob([t.data])),a=document.createElement("a");a.href=s,a.download=`${this.extensionData.id||"lnbits-extension"}.zip`,document.body.appendChild(a),a.click(),a.remove(),window.URL.revokeObjectURL(s)}catch(e){LNbits.utils.notifyApiError(e)}},async buildExtensionAndDeploy(){try{const{data:e}=await LNbits.api.request("POST","/api/v1/extension/builder/deploy",null,this.extensionData);Quasar.Notify.create({message:e.message||"Extension deployed!",color:"positive"})}catch(e){LNbits.utils.notifyApiError(e)}},async cleanCacheData(){LNbits.utils.confirmDialog("Are you sure you want to clean the cache data? This action cannot be undone.","Clean Cache Data").onOk(async()=>{try{const{data:e}=await LNbits.api.request("DELETE","/api/v1/extension/builder",null,{});Quasar.Notify.create({message:e.message||"Cache data cleaned!",color:"positive"})}catch(e){LNbits.utils.notifyApiError(e)}})},async previewExtension(e){this.saveState();try{await LNbits.api.request("POST","/api/v1/extension/builder/preview",null,{...this.extensionData,preview_action:{is_preview_mode:!!e,is_settings_preview:"settings"===e,is_owner_data_preview:"owner_data"===e,is_client_data_preview:"client_data"===e,is_public_page_preview:"public_page"===e}}),this.refreshIframe(e)}catch(e){LNbits.utils.notifyApiError(e)}},async refreshPreview(){setTimeout(()=>{const e=this.previewStepNames[`${this.step}`]||"";e&&this.previewExtension(e)},100)},async getStubExtensionReleases(){try{const e="extension_builder_stub",{data:t}=await LNbits.api.request("GET",`/api/v1/extension/${e}/releases`);this.extensionStubVersions=t;const s=t.filter(e=>e.is_version_compatible);this.extensionData.stub_version=s[0]?s[0].version:""}catch(e){LNbits.utils.notifyApiError(e)}},refreshIframe(e=""){const t=this.$refs[`iframeStep${this.step}`];if(!t)return void console.warn("Extension Builder Preview iframe not loaded yet.");t.onload=()=>{const e=t.contentDocument||t.contentWindow.document;e.body.style.transform="scale(0.8)",e.body.style.transformOrigin="center top"};let s="Page"+this.extensionData.id.toLowerCase().split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("");"public_page"===e&&(s+="Public"),t.src=`/extensions/builder/preview?ext_id=${this.extensionData.id}&page=${e}&component=${s}`},initBasicData(){this.extensionData.owner_data.fields=[JSON.parse(JSON.stringify(this.sampleField))],this.extensionData.client_data.fields=[JSON.parse(JSON.stringify(this.sampleField))],this.extensionData.settings_data.fields=[JSON.parse(JSON.stringify(this.sampleField))],this.extensionDataCleanString=JSON.stringify(this.extensionData)}},created(){this.initBasicData();const e=this.$q.localStorage.getItem("lnbits.extension.builder.data");e&&(this.extensionData={...this.extensionData,...JSON.parse(e)});const t=+this.$q.localStorage.getItem("lnbits.extension.builder.step");t&&(this.step=t),this.g.user.admin&&this.getStubExtensionReleases(),setTimeout(()=>{this.refreshIframe()},1e3)}},window.PageExtensionBuilder={template:"#page-extension-builder",data:()=>({step:1,previewStepNames:{2:"settings",3:"owner_data",4:"client_data",5:"public_page"},extensionDataCleanString:"",extensionData:{id:"",name:"",stub_version:"",short_description:"",description:"",public_page:{has_public_page:!0,owner_data_fields:{name:"",description:""},client_data_fields:{public_inputs:[]},action_fields:{generate_action:!0,generate_payment_logic:!1,wallet_id:"",currency:"",amount:"",paid_flag:""}},preview_action:{is_preview_mode:!1,is_settings_preview:!1,is_owner_data_preview:!1,is_client_data_preview:!1,is_public_page_preview:!1},settings_data:{name:"Settings",enabled:!0,type:"user",fields:[]},owner_data:{name:"OwnerData",fields:[]},client_data:{enabled:!0,name:"ClientData",fields:[]}},sampleField:{name:"name",type:"str",label:"Name",hint:"",optional:!0,editable:!0,searchable:!0,sortable:!0},settingsTypes:[{label:"User Settings",value:"user"},{label:"Admin Settings",value:"admin"}],amountSource:[{label:"Client Data",value:"client_data"},{label:"Owner Data",value:"owner_data"}],extensionStubVersions:[]}),watch:{"extensionData.public_page.action_fields.amount_source":function(e,t){t&&e!==t&&(this.extensionData.public_page.action_fields.amount="")}},computed:{paymentActionAmountFields(){const e=this.extensionData.public_page.action_fields.amount_source;return e?"owner_data"===e?[""].concat(this.extensionData.owner_data.fields.filter(e=>"int"===e.type||"float"===e.type).map(e=>e.name)):"client_data"===e?[""].concat(this.extensionData.client_data.fields.filter(e=>"int"===e.type||"float"===e.type).map(e=>e.name)):void 0:[""]}},methods:{saveState(){this.$q.localStorage.set("lnbits.extension.builder.data",JSON.stringify(this.extensionData)),this.$q.localStorage.set("lnbits.extension.builder.step",this.step)},nextStep(){this.saveState(),this.$refs.stepper.next(),this.refreshPreview()},previousStep(){this.saveState(),this.$refs.stepper.previous(),this.refreshPreview()},onStepChange(){this.saveState(),this.refreshPreview()},clearAllData(){LNbits.utils.confirmDialog("Are you sure you want to clear all data? This action cannot be undone.").onOk(()=>{this.extensionData=JSON.parse(this.extensionDataCleanString),this.$q.localStorage.remove("lnbits.extension.builder.data"),this.$refs.stepper.set(1)})},exportJsonData(){!0!==Quasar.exportFile(`${this.extensionData.id||"data-export"}.json`,JSON.stringify(this.extensionData,null,2),"text/json")?Quasar.Notify.create({message:"Browser denied file download...",color:"negative",icon:null}):Quasar.Notify.create({message:"File downloaded!",color:"positive",icon:"file_download"})},onJsonDataInput(e){const t=e.target.files[0],s=new FileReader;s.onload=e=>{this.extensionData={...this.extensionData,...JSON.parse(e.target.result)},this.$refs.extensionDataInput.value=null,Quasar.Notify.create({message:"File loaded!",color:"positive",icon:"file_upload"})},s.readAsText(t)},async buildExtension(){try{const e={responseType:"blob"},t=await LNbits.api.request("POST","/api/v1/extension/builder/zip",null,this.extensionData,e),s=window.URL.createObjectURL(new Blob([t.data])),a=document.createElement("a");a.href=s,a.download=`${this.extensionData.id||"lnbits-extension"}.zip`,document.body.appendChild(a),a.click(),a.remove(),window.URL.revokeObjectURL(s)}catch(e){LNbits.utils.notifyApiError(e)}},async buildExtensionAndDeploy(){try{const{data:e}=await LNbits.api.request("POST","/api/v1/extension/builder/deploy",null,this.extensionData);Quasar.Notify.create({message:e.message||"Extension deployed!",color:"positive"})}catch(e){LNbits.utils.notifyApiError(e)}},async cleanCacheData(){LNbits.utils.confirmDialog("Are you sure you want to clean the cache data? This action cannot be undone.","Clean Cache Data").onOk(async()=>{try{const{data:e}=await LNbits.api.request("DELETE","/api/v1/extension/builder",null,{});Quasar.Notify.create({message:e.message||"Cache data cleaned!",color:"positive"})}catch(e){LNbits.utils.notifyApiError(e)}})},async previewExtension(e){this.saveState();try{await LNbits.api.request("POST","/api/v1/extension/builder/preview",null,{...this.extensionData,preview_action:{is_preview_mode:!!e,is_settings_preview:"settings"===e,is_owner_data_preview:"owner_data"===e,is_client_data_preview:"client_data"===e,is_public_page_preview:"public_page"===e}}),this.refreshIframe(e)}catch(e){LNbits.utils.notifyApiError(e)}},async refreshPreview(){setTimeout(()=>{const e=this.previewStepNames[`${this.step}`]||"";e&&this.previewExtension(e)},100)},async getStubExtensionReleases(){try{const e="extension_builder_stub",{data:t}=await LNbits.api.request("GET",`/api/v1/extension/${e}/releases`);this.extensionStubVersions=t;const s=t.filter(e=>e.is_version_compatible);this.extensionData.stub_version=s[0]?s[0].version:""}catch(e){LNbits.utils.notifyApiError(e)}},refreshIframe(e=""){const t=this.$refs[`iframeStep${this.step}`];if(!t)return void console.warn("Extension Builder Preview iframe not loaded yet.");t.onload=()=>{const e=t.contentDocument||t.contentWindow.document;e.body.style.transform="scale(0.8)",e.body.style.transformOrigin="center top"};let s="Page"+this.extensionData.id.toLowerCase().split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("");"public_page"===e&&(s+="Public"),t.src=`/extensions/builder/preview?ext_id=${this.extensionData.id}&page=${e}&component=${s}`},initBasicData(){this.extensionData.owner_data.fields=[JSON.parse(JSON.stringify(this.sampleField))],this.extensionData.client_data.fields=[JSON.parse(JSON.stringify(this.sampleField))],this.extensionData.settings_data.fields=[JSON.parse(JSON.stringify(this.sampleField))],this.extensionDataCleanString=JSON.stringify(this.extensionData)}},created(){this.initBasicData();const e=this.$q.localStorage.getItem("lnbits.extension.builder.data");e&&(this.extensionData={...this.extensionData,...JSON.parse(e)});const t=+this.$q.localStorage.getItem("lnbits.extension.builder.step");t&&(this.step=t),this.g.user.admin&&this.getStubExtensionReleases(),setTimeout(()=>{this.refreshIframe()},1e3)}},window.PageExtensionBuilderPreview={template:"#page-extension-builder-preview",mixins:[windowMixin],watch:{name:"reload"},data:()=>({extId:"",pageName:"",componentName:null}),methods:{async reload(){await LNbits.utils.loadTemplate(`/extensions/builder/preview/${this.extId}/template?page_name=${this.pageName}`),await LNbits.utils.loadScript(`/extensions/builder/preview/${this.extId}/component?page_name=${this.pageName}`),this._component=window[this.componentName],console.log("LNbits preview reloaded componentName:",this.componentName,!!this._component),this.$forceUpdate()}},async created(){const e=new URLSearchParams(window.location.search);this.extId=e.get("ext_id")||"",this.pageName=e.get("page")||"",this.componentName=e.get("component")||"",await this.reload()},render(){return this._component?Vue.h(this._component):Vue.h("div","Loading...")}};const EXTENSION_PERMISSION_DEFAULT_MAX_ROWS_PER_SOURCE=1e4,EXTENSION_PERMISSION_MAX_ROWS_PER_SOURCE_LIMIT=1e6,EXTENSION_PERMISSION_MAX_MESSAGES_PER_SECOND_LIMIT=100;window.PageExtensions={template:"#page-extensions",data(){return{extbuilderEnabled:!1,slide:0,fullscreen:!1,autoplay:!0,searchTerm:"",tab:"installed",manageExtensionTab:"releases",filteredExtensions:[],categories:new Set,updatableExtensions:[],showUninstallDialog:!1,showManageExtensionDialog:!1,showExtensionDetailsDialog:!1,showDropDbDialog:!1,showPayToEnableDialog:!1,showUpdateAllDialog:!1,dropDbExtensionId:"",selectedExtension:null,selectedImage:null,selectedExtensionDetails:null,selectedExtensionRepos:null,selectedRelease:null,permissionGrant:{show:!1,permissions:[],resolve:null},extensionPermissionMaxRowsPerSourceLimit:1e6,extensionPermissionMaxMessagesPerSecondLimit:100,managedExtensionPermissions:{loading:!1,extensionPermissions:[],userPermissions:{},savingExtensionPermissions:!1,savingKey:"",deletingKey:""},backgroundPaymentDestinationOptions:[{label:"Only transfers to my wallets",value:"own_wallets_only"},{label:"Allow external payments",value:"external_allowed"}],uninstallAndDropDb:!1,maxStars:5,paylinkWebsocket:null,searchToggle:!1,reviewsUrl:null,reviewsDialog:{show:!1,extension:null,loading:!1,submitting:!1,form:{name:"",rating:0,comment:""},error:null},reviews:[],reviewsTable:{loading:!1,columns:[{name:"name",align:"left",label:this.$t("Name"),field:"name",sortable:!0},{name:"comment",align:"left",label:this.$t("Comment"),field:"comment"},{name:"created_at",align:"left",label:this.$t("Date"),field:"created_at"},{name:"rating",align:"right",label:"Rating",field:"rating"}],pagination:{rowsPerPage:5,sortBy:"created_at",descending:!0,page:1}},paymentDialog:{show:!1,invoice:"",hash:""}}},watch:{searchTerm(e){this.filterExtensions(e,this.tab)},tab(e){this.filterExtensions(this.searchTerm,e)}},computed:{managedUserPermissionRows(){const e=[],t=this.managedExtensionPermissions.userPermissions||{};return Object.entries(t).forEach(([t,s])=>{Array.isArray(s)&&s.forEach(s=>{if(!s||"object"!=typeof s)return;const a=String(s.id||""),i=String(s.wallet_id||"");a&&i&&e.push({key:a,permissionId:t,label:this.permissionLabelById(t),grantId:a,walletId:i,walletName:this.walletName(i),grant:s})})}),e}},methods:{filterExtensions(e,t){const s=!["installed","all","featured"].includes(t);var a;this.filteredExtensions=this.extensions.filter(e=>"all"!==t||!e.isInstalled).filter(e=>"installed"!==t||e.isInstalled).filter(e=>"installed"!==t||(!!e.isActive||!!this.g.user.admin)).filter(e=>"featured"!==t||e.isFeatured).filter(e=>!s||(e=>e.categories?.includes(t)??!1)(e)).filter((a=e,function(e){return e.name.toLowerCase().includes(a.toLowerCase())||e.shortDescription?.toLowerCase().includes(a.toLowerCase())})).map(e=>({...e,details_link:e.installedRelease?.details_link||e.latestRelease?.details_link}))},async installExtension(e){this.unsubscribeFromPaylinkWs();const t=await this.resolveExtensionPermissionGrant(e);null!==t&&(this.selectedExtension.inProgress=!0,this.showManageExtensionDialog=!1,e.payment_hash=e.payment_hash||this.getPaylinkHash(e.pay_link),LNbits.api.request("POST","/api/v1/extension",this.g.user.wallets[0].adminkey,{ext_id:this.selectedExtension.id,archive:e.archive,source_repo:e.source_repo,payment_hash:e.payment_hash,version:e.version,permissions:t}).then(t=>{this.selectedExtension.inProgress=!1;const s=this.extensions.find(e=>e.id===this.selectedExtension.id);s.isAvailable=!0,s.isInstalled=!0,s.isWasm=!0===t.data.is_wasm||!0===t.data.isWasm||"wasm"===e.extension_type||!0===s.isWasm,s.icon=t.data.icon||s.icon,s.installedRelease=e,this.toggleExtension(s),s.inProgress=!1,this.selectedExtension=s,this.extensions=this.extensions.concat([]),this.tab="installed"}).catch(e=>{console.warn(e),this.selectedExtension.inProgress=!1,LNbits.utils.notifyApiError(e)}))},async uninstallExtension(){this.showManageExtensionDialog=!1,this.showUninstallDialog=!1,this.selectedExtension.inProgress=!0,LNbits.api.request("DELETE",`/api/v1/extension/${this.selectedExtension.id}`,this.g.user.wallets[0].adminkey).then(e=>{const t=this.extensions.find(e=>e.id===this.selectedExtension.id);t.isAvailable=!1,t.isInstalled=!1,t.inProgress=!1,t.installedRelease=null,this.filteredExtensions=this.filteredExtensions.filter(e=>e.id!==t.id),Quasar.Notify.create({type:"positive",message:"Extension uninstalled!"}),this.uninstallAndDropDb&&this.showDropDb()}).catch(e=>{LNbits.utils.notifyApiError(e),extension.inProgress=!1})},async dropExtensionDb(){const e=this.selectedExtension;this.showManageExtensionDialog=!1,this.showDropDbDialog=!1,this.dropDbExtensionId="",e.inProgress=!0,LNbits.api.request("DELETE",`/api/v1/extension/${e.id}/db`,this.g.user.wallets[0].adminkey).then(t=>{e.installedRelease=null,e.inProgress=!1,e.hasDatabaseTables=!1,Quasar.Notify.create({type:"positive",message:"Extension DB deleted!"})}).catch(t=>{LNbits.utils.notifyApiError(t),e.inProgress=!1})},toggleExtension(e){const t=e.isActive?"activate":"deactivate";LNbits.api.request("PUT",`/api/v1/extension/${e.id}/${t}`,this.g.user.wallets[0].adminkey).then(s=>{Quasar.Notify.create({timeout:2e3,type:"positive",message:`Extension '${e.id}' ${t}d!`})}).catch(t=>{LNbits.utils.notifyApiError(t),e.isActive=!1,e.inProgress=!1})},async enableExtensionForUser(e){e.isPaymentRequired?this.showPayToEnable(e):this.enableExtension(e)},async enableExtension(e){LNbits.api.request("PUT",`/api/v1/extension/${e.id}/enable`,this.g.user.wallets[0].adminkey).then(t=>{this.g.user.extensions=this.g.user.extensions.concat([e.id]),Quasar.Notify.create({type:"positive",message:"Extension enabled!"})}).catch(e=>{console.warn(e),LNbits.utils.notifyApiError(e)})},disableExtension(e){LNbits.api.request("PUT",`/api/v1/extension/${e.id}/disable`,this.g.user.wallets[0].adminkey).then(t=>{this.g.user.extensions=this.g.user.extensions.filter(t=>t!==e.id),Quasar.Notify.create({type:"positive",message:"Extension disabled!"})}).catch(e=>{console.warn(error),LNbits.utils.notifyApiError(e)})},showPayToEnable(e){this.selectedExtension=e,this.selectedExtension.payToEnable.paidAmount=e.payToEnable.amount,this.selectedExtension.payToEnable.showQRCode=!1,this.showPayToEnableDialog=!0},updatePayToInstallData(e){LNbits.api.request("PUT",`/api/v1/extension/${e.id}/sell`,this.g.user.wallets[0].adminkey,{required:e.payToEnable.required,amount:e.payToEnable.amount,wallet:e.payToEnable.wallet}).then(e=>{Quasar.Notify.create({type:"positive",message:"Payment info updated!"}),this.showManageExtensionDialog=!1}).catch(t=>{LNbits.utils.notifyApiError(t),e.inProgress=!1})},showUninstall(){this.showManageExtensionDialog=!1,this.showUninstallDialog=!0,this.uninstallAndDropDb=!1},showDropDb(){this.showDropDbDialog=!0},async showManageExtension(e){if(this.selectedExtension=e,this.selectedRelease=null,this.selectedExtensionRepos=null,this.resetManagedExtensionPermissions(),this.manageExtensionTab=this.g.user.admin?"releases":"extension-permissions",this.showManageExtensionDialog=!0,this.canManageExtensionPermissions(e)&&this.loadManagedExtensionPermissions(e),this.g.user.admin)try{const{data:t}=await LNbits.api.request("GET",`/api/v1/extension/${e.id}/releases`);this.selectedExtensionRepos=t.reduce((e,t)=>(e[t.source_repo]=e[t.source_repo]||{releases:[],isInstalled:!1,repo:t.repo},t.inProgress=!1,t.error=null,t.loaded=!1,t.isInstalled=this.isInstalledVersion(this.selectedExtension,t),t.isInstalled&&(e[t.source_repo].isInstalled=!0),t.pay_link&&(t.requiresPayment=!0,t.paidAmount=t.cost_sats,t.payment_hash=this.getPaylinkHash(t.pay_link)),e[t.source_repo].releases.push(t),e),{})}catch(t){LNbits.utils.notifyApiError(t),e.inProgress=!1}},canShowManageExtensionButton(e){return this.g.user.admin||!0===e?.isWasm&&!0===e?.isInstalled},canManageExtensionPermissions(e=this.selectedExtension){return!0===e?.isWasm&&!0===e?.isInstalled},canShowAdminManageTabs(){return!0===this.g.user.admin},resetManagedExtensionPermissions(){this.managedExtensionPermissions={loading:!1,extensionPermissions:[],userPermissions:{},savingExtensionPermissions:!1,savingKey:"",deletingKey:""}},async loadManagedExtensionPermissions(e=this.selectedExtension){if(this.canManageExtensionPermissions(e)){this.managedExtensionPermissions.loading=!0;try{const{data:t}=await LNbits.api.request("GET",`/api/v1/extension/${e.id}/permissions`);this.managedExtensionPermissions.extensionPermissions=this.cloneEditableExtensionPermissions(t.extension_permissions||[]),this.managedExtensionPermissions.userPermissions=this.cloneUserPermissions(t.user_permissions||{})}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}finally{this.managedExtensionPermissions.loading=!1}}},cloneEditableExtensionPermissions(e){return(e||[]).filter(e=>e&&"object"==typeof e).map(e=>({...e,policies:Array.isArray(e.policies)?e.policies.map(t=>this.cloneEditablePermissionPolicy(e.id,t)):e.policies}))},cloneEditablePermissionPolicy(e,t){if(!t||"object"!=typeof t||Array.isArray(t))return t;const s=Object.entries(t).reduce((e,[t,s])=>({...e,[t]:Array.isArray(s)?s.slice():s}),{});return"ext.storage.append_public"===e&&(s.max_rows_per_source=this.maxRowsPerSourceValue(s.max_rows_per_source,1e4)),"websocket.publish"===e&&(s.max_messages_per_second=Number(s.max_messages_per_second)),s},maxRowsPerSourceValue(e,t){const s=Number(e);return!Number.isInteger(s)||s<=0?t:Math.min(s,1e6)},extensionPermissionLimitError(e){const t=(e||[]).find(e=>"ext.storage.append_public"===e?.id);if(!t||!Array.isArray(t.policies))return this.websocketPublishLimitError(e);for(const e of t.policies){if(!e||"object"!=typeof e)continue;const t=Number(e.max_rows_per_source);if(!Number.isInteger(t)||t<=0)return"Max rows per source must be a positive integer.";if(t>1e6)return"Max rows per source cannot exceed 1000000."}return this.websocketPublishLimitError(e)},websocketPublishLimitError(e){const t=(e||[]).find(e=>"websocket.publish"===e?.id);if(!t)return"";if(!Array.isArray(t.policies)||1!==t.policies.length)return"Websocket publish requires a max messages per second policy.";const s=t.policies[0];if(!s||"object"!=typeof s)return"Websocket publish requires a max messages per second policy.";const a=Number(s.max_messages_per_second);return!Number.isInteger(a)||a<=0?"Max messages per second must be a positive integer.":a>100?"Max messages per second cannot exceed 100.":""},validateExtensionPermissionLimits(e){const t=this.extensionPermissionLimitError(e);return!t||(Quasar.Notify.create({type:"negative",message:t}),!1)},extensionPermissionsHaveEditableLimits:e=>(e||[]).some(e=>"ext.storage.append_public"===e?.id&&Array.isArray(e.policies)&&e.policies.length>0||"websocket.publish"===e?.id),async saveManagedExtensionPermissions(){const e=this.managedExtensionPermissions.extensionPermissions;if(this.validateExtensionPermissionLimits(e)){this.managedExtensionPermissions.savingExtensionPermissions=!0;try{const{data:t}=await LNbits.api.request("PUT",`/api/v1/extension/${this.selectedExtension.id}/permissions`,this.g.user.wallets[0].adminkey,{permissions:this.cloneEditableExtensionPermissions(e)});this.managedExtensionPermissions.extensionPermissions=this.cloneEditableExtensionPermissions(t.extension_permissions||[]),Quasar.Notify.create({type:"positive",message:"Permission updated."})}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}finally{this.managedExtensionPermissions.savingExtensionPermissions=!1}}},cloneUserPermissions(e){const t={};return Object.entries(e||{}).forEach(([e,s])=>{Array.isArray(s)&&(t[e]=s.filter(e=>e&&"object"==typeof e).map(e=>({...e,_original:{...e}})))}),t},async showExtensionDetails(e,t){if(t){this.selectedExtension=this.extensions.find(t=>t.id===e)||this.selectedExtension,this.selectedExtensionDetails=null,this.showExtensionDetailsDialog=!0,this.slide=0,this.fullscreen=!1;try{const{data:s}=await LNbits.api.request("GET",`/api/v1/extension/${e}/details?details_link=${t}`);this.selectedExtensionDetails=s,this.selectedExtensionDetails.description_md=LNbits.utils.convertMarkdown(s.description_md)}catch(e){console.warn(e)}}},async payAndInstall(e){try{if(null===await this.resolveExtensionPermissionGrant(e))return;this.selectedExtension.inProgress=!0,this.showManageExtensionDialog=!1;const t=await this.requestPaymentForInstall(this.selectedExtension.id,e);this.rememberPaylinkHash(e.pay_link,t.payment_hash);const s=this.g.user.wallets.find(t=>t.id===e.wallet),{data:a}=await LNbits.api.payInvoice(s,t.payment_request);e.payment_hash=a.payment_hash,await this.installExtension(e)}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}finally{this.selectedExtension.inProgress=!1}},async payAndEnable(e){try{const t=await this.requestPaymentForEnable(e.id,e.payToEnable.paidAmount),s=this.g.user.wallets.find(t=>t.id===e.payToEnable.paymentWallet),{data:a}=await LNbits.api.payInvoice(s,t.payment_request);this.enableExtension(e),this.showPayToEnableDialog=!1}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}},async showInstallQRCode(e){if(null!==await this.resolveExtensionPermissionGrant(e)){this.selectedRelease=e;try{const t=await this.requestPaymentForInstall(this.selectedExtension.id,e);this.selectedRelease.paymentRequest=t.payment_request,this.selectedRelease.payment_hash=t.payment_hash,this.selectedRelease=_.clone(this.selectedRelease),this.rememberPaylinkHash(this.selectedRelease.pay_link,this.selectedRelease.payment_hash),this.subscribeToPaylinkWs(this.selectedRelease.pay_link,t.payment_hash)}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}}},async showEnableQRCode(e){try{e.payToEnable.showQRCode=!0,this.selectedExtension=_.clone(e);const t=await this.requestPaymentForEnable(e.id,e.payToEnable.paidAmount);e.payToEnable.paymentRequest=t.payment_request,this.selectedExtension=_.clone(e);const s=new URL(window.location);s.protocol="https:"===s.protocol?"wss":"ws",s.pathname=`/api/v1/ws/${t.payment_hash}`;const a=new WebSocket(s);a.addEventListener("message",async({data:t})=>{!1===JSON.parse(t).pending&&(Quasar.Notify.create({type:"positive",message:"Invoice Paid!"}),this.enableExtension(e),a.close())})}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}},async requestPaymentForInstall(e,t){const{data:s}=await LNbits.api.request("PUT",`/api/v1/extension/${e}/invoice/install`,null,{ext_id:e,archive:t.archive,source_repo:t.source_repo,cost_sats:t.paidAmount,version:t.version});return s},async requestPaymentForEnable(e,t){const{data:s}=await LNbits.api.request("PUT",`/api/v1/extension/${e}/invoice/enable`,null,{amount:t});return s},clearHangingInvoice(e){this.forgetPaylinkHash(e.pay_link),e.payment_hash=null},rememberPaylinkHash(e,t){this.$q.localStorage.set(`lnbits.extensions.paylink.${e}`,t)},getPaylinkHash(e){return this.$q.localStorage.getItem(`lnbits.extensions.paylink.${e}`)},forgetPaylinkHash(e){this.$q.localStorage.remove(`lnbits.extensions.paylink.${e}`)},subscribeToPaylinkWs(e,t){const s=new URL(`${e}/${t}`);s.protocol="https:"===s.protocol?"wss":"ws",this.paylinkWebsocket=new WebSocket(s),this.paylinkWebsocket.addEventListener("message",async({data:e})=>{JSON.parse(e).paid?(Quasar.Notify.create({type:"positive",message:"Invoice Paid!"}),this.installExtension(this.selectedRelease)):Quasar.Notify.create({type:"warning",message:"Invoice tracking lost!"})})},unsubscribeFromPaylinkWs(){try{this.paylinkWebsocket&&this.paylinkWebsocket.close()}catch(e){console.warn(e)}},hasNewVersion(e){if(e.installedRelease&&e.latestRelease)return e.installedRelease.version!==e.latestRelease.version},isInstalledVersion(e,t){if(e.installedRelease)return e.installedRelease.source_repo===t.source_repo&&e.installedRelease.version===t.version},getReleaseIcon:e=>e.is_version_compatible?e.isInstalled?"download_done":"download":"block",getReleaseIconColor:e=>e.is_version_compatible?e.isInstalled?"text-green":"":"text-red",extensionOpenUrl:e=>e.isWasm?`/ext/${e.id}`:`/${e.id}`,permissionLabelById(e){const t=`extension_permission_${String(e).replace(/[^A-Za-z0-9]/g,"_")}`,s=this.$t(t);return s===t?e:s},walletName(e){const t=(this.g.user.wallets||[]).find(t=>t.id===e);return t?t.name||t.id:e},userPermissionRowCaption:e=>`${e.walletName} (${e.walletId.slice(0,8)}...)`,isBackgroundPaymentPermission:e=>"wallet.pay_invoice_background"===e.permissionId,userPermissionGrantPayload(e){return{wallet_id:e.walletId,max_amount:this.positiveInteger(e.grant.max_amount,0),destination_policy:this.backgroundPaymentDestinationPolicy(e.grant.destination_policy)}},positiveInteger(e,t){const s=Number(e);return!Number.isFinite(s)||s<=0?t:Math.floor(s)},backgroundPaymentDestinationPolicy:e=>"external_allowed"===e?"external_allowed":"own_wallets_only",backgroundPaymentGrantIncreased(e,t){const s=e.grant._original||{},a=this.positiveInteger(s.max_amount,0),i=this.backgroundPaymentDestinationPolicy(s.destination_policy);return t.max_amount>a||"own_wallets_only"===i&&"external_allowed"===t.destination_policy},confirmUserPermissionIncrease:()=>new Promise(e=>{let t=!1;const s=s=>{t||(t=!0,e(s))};LNbits.utils.confirmDialog("This increases what the extension can do with this wallet. Continue?").onOk(()=>s(!0)).onCancel(()=>s(!1)).onDismiss(()=>s(!1))}),async saveUserPermissionGrant(e){if(!this.isBackgroundPaymentPermission(e))return;const t=this.userPermissionGrantPayload(e);if(t.max_amount){if(!this.backgroundPaymentGrantIncreased(e,t)||await this.confirmUserPermissionIncrease()){this.managedExtensionPermissions.savingKey=e.key;try{await LNbits.api.request("POST",`/api/v1/extension/${this.selectedExtension.id}/permissions/background-payment`,null,t),Quasar.Notify.create({type:"positive",message:"Permission updated."}),await this.loadManagedExtensionPermissions()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}finally{this.managedExtensionPermissions.savingKey=""}}}else Quasar.Notify.create({type:"negative",message:"Max payment amount must be greater than zero."})},deleteUserPermissionGrant(e){LNbits.utils.confirmDialog("Remove this permission grant?").onOk(async()=>{this.managedExtensionPermissions.deletingKey=e.key;try{const t=encodeURIComponent(e.grantId);await LNbits.api.request("DELETE",`/api/v1/extension/${this.selectedExtension.id}/permissions/user/${t}`),Quasar.Notify.create({type:"positive",message:"Permission removed."}),await this.loadManagedExtensionPermissions()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}finally{this.managedExtensionPermissions.deletingKey=""}})},async getGitHubReleaseDetails(e){if(!e.is_github_release||e.loaded)return;const[t,s]=e.source_repo.split("/");e.inProgress=!0;try{const{data:a}=await LNbits.api.request("GET",`/api/v1/extension/release/${t}/${s}/${e.version}`);e.loaded=!0,e.is_version_compatible=a.is_version_compatible,e.min_lnbits_version=a.min_lnbits_version,e.warning=a.warning,e.extension_type=a.extension_type,e.permissions=a.permissions||[]}catch(t){console.warn(t),e.error=t,LNbits.utils.notifyApiError(t)}finally{e.inProgress=!1}},async resolveExtensionPermissionGrant(e){const t=this.extensionPermissionsForRelease(e);if(!this.releaseRequiresPermissionGrant(e)||!t.length)return[];if(e.grantedPermissions)return e.grantedPermissions;const s=await this.confirmExtensionPermissions(t);return s?(e.grantedPermissions=s,s):null},extensionPermissionsForRelease(e){return e.permissions||this.selectedExtension?.permissions||[]},releaseRequiresPermissionGrant(e){return"wasm"===e.extension_type||!0===this.selectedExtension?.isWasm},confirmExtensionPermissions(e){return new Promise(t=>{this.selectedRelease=null,this.permissionGrant={show:!0,permissions:this.cloneEditableExtensionPermissions(e),resolve:t},this.showManageExtensionDialog=!0})},grantExtensionPermissions(){this.validateExtensionPermissionLimits(this.permissionGrant.permissions)&&this.resolveExtensionPermissionDialog(this.cloneEditableExtensionPermissions(this.permissionGrant.permissions))},cancelExtensionPermissions(){this.resolveExtensionPermissionDialog(null)},onManageExtensionDialogHide(){this.permissionGrant.show&&this.resolveExtensionPermissionDialog(null)},resolveExtensionPermissionDialog(e){const t=this.permissionGrant.resolve;this.permissionGrant={show:!1,permissions:[],resolve:null},this.showManageExtensionDialog=!1,t&&t(e)},permissionGrantHasHighRisk(){return window.LNbitsExtensionPermissions.hasHighRisk({permissions:this.permissionGrant.permissions,extensions:this.extensions,translate:e=>this.$t(e)})},async selectAllUpdatableExtensionss(){this.updatableExtensions.forEach(e=>e.selectedForUpdate=!0)},async updateSelectedExtensions(){let e=0;for(const t of this.updatableExtensions)try{if(!t.selectedForUpdate)continue;if(t.isWasm){Quasar.Notify.create({type:"warning",message:`Skipping ${t.id}; this extension update requires permission approval.`});continue}t.inProgress=!0,await LNbits.api.request("POST","/api/v1/extension",null,{ext_id:t.id,archive:t.latestRelease.archive,source_repo:t.latestRelease.source_repo,payment_hash:t.latestRelease.payment_hash,version:t.latestRelease.version}),e++,t.isAvailable=!0,t.isInstalled=!0,t.isUpgraded=!0,t.inProgress=!1,t.installedRelease=t.latestRelease,t.isActive=!0,this.toggleExtension(t)}catch(e){console.warn(e),Quasar.Notify.create({type:"negative",message:`Failed to update ${t.id}!`})}finally{t.inProgress=!1}Quasar.Notify.create({type:e?"positive":"warning",message:`${e||"No"} extensions updated!`}),this.showUpdateAllDialog=!1},formatAvg(e){const t=Number(e||0);return Math.round(t/2/100*2)/2},async loadReviewStats(){if(this.reviewsUrl)try{const{data:e}=await LNbits.api.request("GET","/api/v1/extension/reviews/tags"),t={};e.forEach(e=>{t[e.tag]=e}),this.extensions.forEach(e=>{e.reviewStats=t[e.id]||null}),this.filterExtensions(this.searchTerm,this.tab)}catch(e){console.warn(e)}else console.info("Extension reviews are not configured")},async openReviews(e){const t=e||(this.selectedExtensionDetails?this.extensions.find(e=>e.id===this.selectedExtensionDetails.id):null);t&&(this.reviewsUrl?(this.reviewsDialog.extension=t,this.selectedExtension=e,this.reviewsDialog.show=!0,await this.getTagReviews()):Quasar.Notify.create({type:"warning",message:this.$t("reviews_url_not_configured")}))},async getTagReviews(e){if(this.reviewsUrl)try{this.reviewsTable.loading=!0;const t=LNbits.utils.prepareFilterQuery(this.reviewsTable,e),{data:s}=await LNbits.api.request("GET",`/api/v1/extension/reviews/${this.selectedExtension.id}?${t}`);this.reviews=s.data,this.reviewsTable.pagination.rowsNumber=s.total}catch(e){LNbits.utils.notifyApiError(e)}finally{this.reviewsTable.loading=!1}else Quasar.Notify.create({type:"warning",message:this.$t("reviews_url_not_configured")})},formatReviewDate(e){if(!e)return"";const t=Number(e);return Number.isNaN(t)?this.utils.formatDate(e):this.utils.formatTimestamp(t)},async submitReview(){if(this.reviewsDialog.extension&&this.reviewsUrl){this.reviewsDialog.submitting=!0;try{const e={tag:this.reviewsDialog.extension.id,name:this.reviewsDialog.form.name,rating:100*this.reviewsDialog.form.rating,comment:this.reviewsDialog.form.comment},{data:t}=await LNbits.api.request("PUT","/api/v1/extension/reviews",null,e);t.payment_request?this.openInvoiceDialog(t.payment_request,t.payment_hash):(Quasar.Notify.create({type:"positive",message:"Review submitted"}),this.resetReviewForm(),await this.getTagReviews(),await this.loadReviewStats())}catch(e){LNbits.utils.notifyApiError(e)}finally{this.reviewsDialog.submitting=!1}}},openInvoiceDialog(e,t){this.paymentDialog.invoice=e,this.paymentDialog.hash=t,this.paymentDialog.show=!0,this.listenForPayment(t)},resetReviewForm(){this.reviewsDialog.form={name:"",rating:0,comment:""},this.paymentDialog={show:!1,invoice:"",hash:""}},listenForPayment(e){try{const t=new URL(this.reviewsUrl);t.protocol="https:"===t.protocol?"wss:":"ws:",t.pathname=`/api/v1/ws/${e}`;const s=new WebSocket(t);s.addEventListener("message",async()=>{Quasar.Notify.create({type:"positive",message:this.$t("reviews_invoice_paid")}),this.paymentDialog.show=!1,this.resetReviewForm(),setTimeout(async()=>{await this.getTagReviews()},1e3),await this.loadReviewStats(),s.close()})}catch(e){console.warn(e)}},async fetchAllExtensions(){try{const{data:e}=await LNbits.api.request("GET","/api/v1/extension/all");return e.forEach(e=>{e.categories?.forEach(e=>this.categories.add(e))}),e}catch(e){return console.warn(e),LNbits.utils.notifyApiError(e),[]}}},async created(){this.extensions=await this.fetchAllExtensions(),this.extbuilderEnabled=this.g.user.admin||this.g.settings.extBuilder,this.reviewsUrl=this.g.settings.extensionsReviewsUrl,0===this.g.user.extensions.length&&(this.tab="all");const e=window.location.hash.replace("#",""),t=this.extensions.find(t=>t.id===e);t&&(this.searchTerm=t.id,t.isInstalled&&(this.tab="installed")),this.updatableExtensions=this.extensions.filter(e=>this.hasNewVersion(e)),await this.loadReviewStats(),this.filterExtensions(this.searchTerm,this.tab)}},window.PageFirstInstall={template:"#page-first-install",data:()=>({loginData:{isPwd:!0,isPwdRepeat:!0,username:"",password:"",passwordRepeat:"",firstInstallToken:""}}),computed:{checkPasswordsMatch(){return this.loginData.password!==this.loginData.passwordRepeat}},methods:{setPassword(){LNbits.api.request("PUT","/api/v1/auth/first_install",null,{username:this.loginData.username,password:this.loginData.password,password_repeat:this.loginData.passwordRepeat,first_install_token:this.loginData.firstInstallToken}).then(async()=>{const e=await LNbits.api.getAuthUser();this.g.user=LNbits.map.user(e.data),this.g.isPublicPage=!1,this.$router.push("/admin")}).catch(this.utils.notifyApiError)}},created(){const e=new URLSearchParams(window.location.search);this.loginData.firstInstallToken=e.get("token")||""}},window.PagePayments={template:"#page-payments",data:()=>({payments:[],dailyChartData:[],searchDate:{from:null,to:null},searchData:{wallet_id:null,payment_hash:null,memo:null,internal_memo:null},statusFilters:{success:!0,pending:!0,failed:!0,incoming:!0,outgoing:!0},chartData:{showPaymentStatus:!0,showPaymentTags:!0,showBalance:!0,showWalletsSize:!1,showBalanceInOut:!1,showPaymentCountInOut:!1},searchOptions:{status:[]},paymentsTable:{columns:[{name:"status",align:"left",label:"Status",field:"status",sortable:!1},{name:"created_at",align:"left",label:"Created At",field:"created_at",sortable:!0},{name:"amount",align:"right",label:"Amount",field:"amount",sortable:!0},{name:"amountFiat",align:"right",label:"Fiat",field:"amountFiat",sortable:!1},{name:"fee_sats",align:"left",label:"Fee",field:"fee_sats",sortable:!0},{name:"tag",align:"left",label:"Tag",field:"tag",sortable:!1},{name:"memo",align:"left",label:"Memo",field:"memo",sortable:!1,max_length:20},{name:"internal_memo",align:"left",label:"Internal Memo",field:"internal_memo",sortable:!1,max_length:20},{name:"wallet_id",align:"left",label:"Wallet (ID)",field:"wallet_id",sortable:!1},{name:"payment_hash",align:"left",label:"Payment Hash",field:"payment_hash",sortable:!1}],pagination:{sortBy:"created_at",rowsPerPage:25,page:1,descending:!0,rowsNumber:10},search:null,hideEmpty:!0,loading:!1},chartsReady:!1,showDetails:!1,paymentDetails:null,lnbitsBalance:0}),async mounted(){this.chartsReady=!0,await this.$nextTick(),this.initCharts(),await this.fetchPayments()},computed:{},methods:{async fetchPayments(e){const t=Object.entries(this.searchData).reduce((e,[t,s])=>s?(e[t]=s,e):e,{});delete t["time[ge]"],delete t["time[le]"],this.searchDate.from&&(t["time[ge]"]=this.searchDate.from+"T00:00:00"),this.searchDate.to&&(t["time[le]"]=this.searchDate.to+"T23:59:59"),this.paymentsTable.filter=t;try{const t=LNbits.utils.prepareFilterQuery(this.paymentsTable,e),{data:s}=await LNbits.api.request("GET",`/api/v1/payments/all/paginated?${t}`);this.paymentsTable.pagination.rowsNumber=s.total,this.payments=s.data.map(e=>(e.extra&&e.extra.tag&&(e.tag=e.extra.tag),e.timeFrom=moment.utc(e.created_at).local().fromNow(),e.outgoing=e.amount<0,e.amount=new Intl.NumberFormat(this.g.locale).format(e.amount/1e3)+" sats",e.extra?.wallet_fiat_amount&&(e.amountFiat=this.formatCurrency(e.extra.wallet_fiat_amount,e.extra.wallet_fiat_currency)),e.extra?.internal_memo&&(e.internal_memo=e.extra.internal_memo),e.fee_sats=new Intl.NumberFormat(this.g.locale).format(e.fee/1e3)+" sats",e))}catch(e){console.error(e),LNbits.utils.notifyApiError(e)}finally{this.updateCharts(e)}},async searchPaymentsBy(e,t){e&&(this.searchData[e]=t),await this.fetchPayments()},clearDateSeach(){this.searchDate={from:null,to:null},delete this.paymentsTable.filter["time[ge]"],delete this.paymentsTable.filter["time[le]"],this.fetchPayments()},searchByDate(){"string"==typeof this.searchDate&&(this.searchDate={from:this.searchDate,to:this.searchDate}),this.searchDate.from&&(this.paymentsTable.filter["time[ge]"]=this.searchDate.from+"T00:00:00"),this.searchDate.to&&(this.paymentsTable.filter["time[le]"]=this.searchDate.to+"T23:59:59"),this.fetchPayments()},handleFilterChanged(){const{success:e,pending:t,failed:s,incoming:a,outgoing:i}=this.statusFilters;delete this.searchData["status[ne]"],delete this.searchData["status[eq]"],e&&t&&s||(e&&t?this.searchData["status[ne]"]="failed":e&&s?this.searchData["status[ne]"]="pending":s&&t?this.searchData["status[ne]"]="success":e?this.searchData["status[eq]"]="success":t?this.searchData["status[eq]"]="pending":s&&(this.searchData["status[eq]"]="failed")),delete this.searchData["amount[ge]"],delete this.searchData["amount[le]"],a&&i||(a?this.searchData["amount[ge]"]="0":i&&(this.searchData["amount[le]"]="0")),this.fetchPayments()},showDetailsToggle(e){return this.paymentDetails=e,this.showDetails=!this.showDetails},formatCurrency(e,t){try{return LNbits.utils.formatCurrency(e,t)}catch(t){return console.error(t),`${e} ???`}},shortify:(e,t=10)=>(valueLength=(e||"").length,valueLength<=t?e:`${e.substring(0,5)}...${e.substring(valueLength-5,valueLength)}`),async updateCharts(e){let t=LNbits.utils.prepareFilterQuery(this.paymentsTable,e);try{const{data:e}=await LNbits.api.request("GET",`/api/v1/payments/stats/count?${t}&count_by=status`);e.sort((e,t)=>e.field-t.field).reverse(),this.searchOptions.status=e.map(e=>e.field),this.paymentsStatusChart.data.datasets[0].data=e.map(e=>e.total),this.paymentsStatusChart.data.labels=[...this.searchOptions.status],this.paymentsStatusChart.update()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}try{const{data:e}=await LNbits.api.request("GET",`/api/v1/payments/stats/wallets?${t}`),s=e.map(e=>e.balance/e.payments_count),a=Math.min(...s),i=Math.max(...s),n=e=>Math.floor(3+22*(e-a)/(i-a)),o=this.randomColors(20),r=e.map((e,t)=>({data:[{x:e.payments_count,y:e.balance,r:n(Math.max(e.balance/e.payments_count,5))}],label:e.wallet_name,wallet_id:e.wallet_id,backgroundColor:o[t%100],hoverOffset:4}));this.paymentsWalletsChart.data.datasets=r,this.paymentsWalletsChart.update()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}try{const{data:e}=await LNbits.api.request("GET",`/api/v1/payments/stats/count?${t}&count_by=tag`);this.searchOptions.tag=e.map(e=>e.field),this.searchOptions.status.sort(),this.paymentsTagsChart.data.datasets[0].data=e.map(e=>e.total),this.paymentsTagsChart.data.labels=e.map(e=>e.field||"core"),this.paymentsTagsChart.update()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}try{const t=Object.entries(this.searchData).reduce((e,[t,s])=>s?(e[t]=s,e):e,{}),s={...this.paymentsTable,filter:t},a=LNbits.utils.prepareFilterQuery(s,e);let{data:i}=await LNbits.api.request("GET",`/api/v1/payments/stats/daily?${a}`);const n=this.searchDate.from+"T00:00:00",o=this.searchDate.to+"T23:59:59";this.lnbitsBalance=i.length?i[i.length-1].balance:0,i=i.filter(e=>this.searchDate.from&&this.searchDate.to?e.date>=n&&e.date<=o:this.searchDate.from?e.date>=n:!this.searchDate.to||e.date<=o),this.paymentsDailyChart.data.datasets=[{label:"Balance",data:i.map(e=>e.balance),pointStyle:!1,borderWidth:2,tension:.7,fill:1},{label:"Fees",data:i.map(e=>e.fee),pointStyle:!1,borderWidth:1,tension:.4,fill:1}],this.paymentsDailyChart.data.labels=i.map(e=>e.date.substring(0,10)),this.paymentsDailyChart.update(),this.paymentsBalanceInOutChart.data.datasets=[{label:"Incoming Payments Balance",data:i.map(e=>e.balance_in)},{label:"Outgoing Payments Balance",data:i.map(e=>e.balance_out)}],this.paymentsBalanceInOutChart.data.labels=i.map(e=>e.date.substring(0,10)),this.paymentsBalanceInOutChart.update(),this.paymentsCountInOutChart.data.datasets=[{label:"Incoming Payments Count",data:i.map(e=>e.count_in)},{label:"Outgoing Payments Count",data:i.map(e=>-e.count_out)}],this.paymentsCountInOutChart.data.labels=i.map(e=>e.date.substring(0,10)),this.paymentsCountInOutChart.update()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}},async initCharts(){const e=this.$q.localStorage.getItem("lnbits.payments.chartData")||{};this.chartData={...this.chartData,...e},this.chartsReady?(this.paymentsStatusChart=new Chart(this.$refs.paymentsStatusChart.getContext("2d"),{type:"doughnut",options:{responsive:!0,maintainAspectRatio:!1,plugins:{title:{display:!1}},onClick:(e,t,s)=>{if(t[0]){const e=t[0].index;this.searchPaymentsBy("status",s.data.labels[e])}}},data:{datasets:[{label:"",data:[],backgroundColor:["rgb(0, 205, 86)","rgb(64, 72, 78)","rgb(255, 99, 132)"],hoverOffset:4}]}}),this.paymentsWalletsChart=new Chart(this.$refs.paymentsWalletsChart.getContext("2d"),{type:"bubble",options:{responsive:!0,maintainAspectRatio:!1,plugins:{legend:{display:!1},title:{display:!1}},onClick:(e,t,s)=>{if(t[0]){const e=t[0].datasetIndex;this.searchPaymentsBy("wallet_id",s.data.datasets[e].wallet_id)}}},data:{datasets:[{label:"",data:[],backgroundColor:this.randomColors(20),hoverOffset:4}]}}),this.paymentsTagsChart=new Chart(this.$refs.paymentsTagsChart.getContext("2d"),{type:"pie",options:{responsive:!0,maintainAspectRatio:!1,plugins:{title:{display:!1},legend:{display:!1,title:{display:!1,text:"Tags"}}},onClick:(e,t,s)=>{if(t[0]){const e=t[0].index;this.searchPaymentsBy("tag",s.data.labels[e])}}},data:{datasets:[{label:"",data:[],backgroundColor:this.randomColors(10),hoverOffset:4}]}}),this.paymentsDailyChart=new Chart(this.$refs.paymentsDailyChart.getContext("2d"),{type:"line",options:{responsive:!0,maintainAspectRatio:!1,plugins:{title:{display:!1},legend:{display:!0,title:{display:!1,text:"Tags"}}}},data:{datasets:[{label:"",data:[],backgroundColor:this.randomColors(10),hoverOffset:4}]}}),this.paymentsBalanceInOutChart=new Chart(this.$refs.paymentsBalanceInOutChart.getContext("2d"),{type:"bar",options:{responsive:!0,maintainAspectRatio:!1,plugins:{title:{display:!1},legend:{display:!0,title:{display:!1,text:"Tags"}}},scales:{x:{stacked:!0},y:{stacked:!0}}},data:{datasets:[{label:"",data:[],backgroundColor:this.randomColors(50),hoverOffset:4}]}}),this.paymentsCountInOutChart=new Chart(this.$refs.paymentsCountInOutChart.getContext("2d"),{type:"bar",options:{responsive:!0,maintainAspectRatio:!1,plugins:{title:{display:!1},legend:{display:!0,title:{display:!1,text:""}}},scales:{x:{stacked:!0},y:{stacked:!0}}},data:{datasets:[{label:"",data:[],backgroundColor:this.randomColors(80),hoverOffset:4}]}})):console.warn("Charts are not ready yet. Initialization delayed.")},saveChartsPreferences(){this.$q.localStorage.set("lnbits.payments.chartData",this.chartData)},randomColors(e=1){const t=[];for(let s=1;s<=10;s++)for(let a=1;a<=10;a++)t.push(`rgb(${a*e*33%200}, ${71*(s+a+e)%255}, ${(s+30*e)%255})`);return t}}},window.PageNode={template:"#page-node",config:{globalProperties:{LNbits:LNbits,msg:"hello"}},data(){return{isSuperUser:!1,wallet:{},tab:"dashboard",payments:1e3,info:{},channel_stats:{},channels:{data:[],filter:""},activeBalance:{},ranks:{},peers:{data:[],filter:""},connectPeerDialog:{show:!1,data:{}},setFeeDialog:{show:!1,data:{fee_ppm:0,fee_base_msat:0}},openChannelDialog:{show:!1,data:{}},closeChannelDialog:{show:!1,data:{}},nodeInfoDialog:{show:!1,data:{}},transactionDetailsDialog:{show:!1,data:{}},states:[{label:"Active",value:"active",color:"green"},{label:"Pending",value:"pending",color:"orange"},{label:"Inactive",value:"inactive",color:"grey"},{label:"Closed",value:"closed",color:"red"}],stateFilters:[{label:"Active",value:"active"},{label:"Pending",value:"pending"}],paymentsTable:{data:[],columns:[{name:"pending",label:""},{name:"date",align:"left",label:this.$t("date"),field:"date",sortable:!0},{name:"sat",align:"right",label:this.$t("amount"),field:e=>this.formatMsat(e.amount),sortable:!0},{name:"fee",align:"right",label:this.$t("fee"),field:"fee"},{name:"destination",align:"right",label:"Destination",field:"destination"},{name:"memo",align:"left",label:this.$t("memo"),field:"memo"}],pagination:{rowsPerPage:10,page:1,rowsNumber:10},filter:null},invoiceTable:{data:[],columns:[{name:"pending",label:""},{name:"paid_at",field:"paid_at",align:"left",label:"Paid at",sortable:!0},{name:"expiry",label:this.$t("expiry"),field:"expiry",align:"left",sortable:!0},{name:"amount",label:this.$t("amount"),field:e=>this.formatMsat(e.amount),sortable:!0},{name:"memo",align:"left",label:this.$t("memo"),field:"memo"}],pagination:{rowsPerPage:10,page:1,rowsNumber:10},filter:null}}},created(){this.getInfo(),this.get1MLStats()},watch:{tab(e){"transactions"!==e||this.paymentsTable.data.length?"channels"!==e||this.channels.data.length||(this.getChannels(),this.getPeers()):(this.getPayments(),this.getInvoices())}},computed:{checkChanges(){return!_.isEqual(this.settings,this.formData)},filteredChannels(){return this.stateFilters?this.channels.data.filter(e=>this.stateFilters.find(({value:t})=>t==e.state)):this.channels.data},totalBalance(){return this.filteredChannels.reduce((e,t)=>(e.local_msat+=t.balance.local_msat,e.remote_msat+=t.balance.remote_msat,e.total_msat+=t.balance.total_msat,e),{local_msat:0,remote_msat:0,total_msat:0})}},methods:{formatMsat:e=>LNbits.utils.formatMsat(e),nodeApi(e,t,s){const a=new URLSearchParams(s?.query);return LNbits.api.request(e,`/node/api/v1${t}?${a}`,{},s?.data).catch(e=>{LNbits.utils.notifyApiError(e)})},getChannel(e){return this.nodeApi("GET",`/channels/${e}`).then(e=>{this.setFeeDialog.data.fee_ppm=e.data.fee_ppm,this.setFeeDialog.data.fee_base_msat=e.data.fee_base_msat})},getChannels(){return this.nodeApi("GET","/channels").then(e=>{this.channels.data=e.data})},getInfo(){return this.nodeApi("GET","/info").then(e=>{this.info=e.data,this.channel_stats=e.data.channel_stats}).catch(()=>{this.info={},this.channel_stats={}})},get1MLStats(){return this.nodeApi("GET","/rank").then(e=>{this.ranks=e.data}).catch(()=>{this.ranks={}})},getPayments(e){e&&(this.paymentsTable.pagination=e.pagination);let t=this.paymentsTable.pagination;const s={limit:t.rowsPerPage,offset:(t.page-1)*t.rowsPerPage??0};return this.nodeApi("GET","/payments",{query:s}).then(e=>{this.paymentsTable.data=e.data.data,this.paymentsTable.pagination.rowsNumber=e.data.total})},getInvoices(e){e&&(this.invoiceTable.pagination=e.pagination);let t=this.invoiceTable.pagination;const s={limit:t.rowsPerPage,offset:(t.page-1)*t.rowsPerPage??0};return this.nodeApi("GET","/invoices",{query:s}).then(e=>{this.invoiceTable.data=e.data.data,this.invoiceTable.pagination.rowsNumber=e.data.total})},getPeers(){return this.nodeApi("GET","/peers").then(e=>{this.peers.data=e.data})},connectPeer(){this.nodeApi("POST","/peers",{data:this.connectPeerDialog.data}).then(()=>{this.connectPeerDialog.show=!1,this.getPeers()})},disconnectPeer(e){LNbits.utils.confirmDialog("Do you really wanna disconnect this peer?").onOk(()=>{this.nodeApi("DELETE",`/peers/${e}`).then(e=>{Quasar.Notify.create({message:"Disconnected",icon:null}),this.needsRestart=!0,this.getPeers()})})},setChannelFee(e){this.nodeApi("PUT",`/channels/${e}`,{data:this.setFeeDialog.data}).then(e=>{this.setFeeDialog.show=!1,this.getChannels()}).catch(LNbits.utils.notifyApiError)},openChannel(){this.nodeApi("POST","/channels",{data:this.openChannelDialog.data}).then(e=>{this.openChannelDialog.show=!1,this.getChannels()}).catch(e=>{console.log(e)})},showCloseChannelDialog(e){this.closeChannelDialog.show=!0,this.closeChannelDialog.data={force:!1,short_id:e.short_id,...e.point}},closeChannel(){this.nodeApi("DELETE","/channels",{query:this.closeChannelDialog.data}).then(e=>{this.closeChannelDialog.show=!1,this.getChannels()})},showSetFeeDialog(e){this.setFeeDialog.show=!0,this.setFeeDialog.channel_id=e,this.getChannel(e)},showOpenChannelDialog(e){this.openChannelDialog.show=!0,this.openChannelDialog.data={peer_id:e,funding_amount:0}},showNodeInfoDialog(e){this.nodeInfoDialog.show=!0,this.nodeInfoDialog.data=e},showTransactionDetailsDialog(e){this.transactionDetailsDialog.show=!0,this.transactionDetailsDialog.data=e},shortenNodeId:e=>e?e.substring(0,5)+"..."+e.substring(e.length-5):"..."}},window.PageNodePublic={template:"#page-node-public",data:()=>({enabled:!1,isSuperUser:!1,wallet:{},tab:"dashboard",payments:1e3,info:{},channel_stats:{},channels:[],activeBalance:{},ranks:{},peers:[],connectPeerDialog:{show:!1,data:{}},openChannelDialog:{show:!1,data:{}},closeChannelDialog:{show:!1,data:{}},nodeInfoDialog:{show:!1,data:{}},states:[{label:"Active",value:"active",color:"green"},{label:"Pending",value:"pending",color:"orange"},{label:"Inactive",value:"inactive",color:"grey"},{label:"Closed",value:"closed",color:"red"}]}),created(){this.getInfo(),this.get1MLStats()},methods:{formatMsat:e=>LNbits.utils.formatMsat(e),api:(e,t,s)=>LNbits.api.request(e,"/node/public/api/v1"+t,{},s),getInfo(){this.api("GET","/info",{}).then(e=>{this.info=e.data,this.channel_stats=e.data.channel_stats,this.enabled=!0}).catch(()=>{this.info={},this.channel_stats={}})},get1MLStats(){this.api("GET","/rank",{}).then(e=>{this.ranks=e.data}).catch(()=>{this.ranks={}})}}},window.PageAudit={template:"#page-audit",data:()=>({chartsReady:!1,auditEntries:[],searchData:{user_id:"",ip_address:"",request_type:"",component:"",request_method:"",response_code:"",path:""},searchOptions:{component:[],request_method:[],response_code:[]},auditTable:{columns:[{name:"created_at",align:"center",label:"Date",field:"created_at",sortable:!0},{name:"duration",align:"left",label:"Duration (sec)",field:"duration",sortable:!0},{name:"component",align:"left",label:"Component",field:"component",sortable:!1},{name:"request_method",align:"left",label:"Method",field:"request_method",sortable:!1},{name:"response_code",align:"left",label:"Code",field:"response_code",sortable:!1},{name:"user_id",align:"left",label:"User Id",field:"user_id",sortable:!1},{name:"ip_address",align:"left",label:"IP Address",field:"ip_address",sortable:!1},{name:"path",align:"left",label:"Path",field:"path",sortable:!1}],pagination:{sortBy:"created_at",rowsPerPage:10,page:1,descending:!0,rowsNumber:10},search:null,hideEmpty:!0,loading:!1},auditDetailsDialog:{data:null,show:!1}}),async created(){},async mounted(){this.chartsReady=!0,await this.$nextTick(),this.initCharts(),await this.fetchAudit()},methods:{async fetchAudit(e){try{const t=LNbits.utils.prepareFilterQuery(this.auditTable,e),{data:s}=await LNbits.api.request("GET",`/audit/api/v1?${t}`);this.auditTable.pagination.rowsNumber=s.total,this.auditEntries=s.data,await this.fetchAuditStats(e)}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}finally{this.auditTable.loading=!1}},async fetchAuditStats(e){try{const t=LNbits.utils.prepareFilterQuery(this.auditTable,e),{data:s}=await LNbits.api.request("GET",`/audit/api/v1/stats?${t}`),a=s.request_method.map(e=>e.field);this.searchOptions.request_method=[...new Set(this.searchOptions.request_method.concat(a))],this.requestMethodChart.data.labels=a,this.requestMethodChart.data.datasets[0].data=s.request_method.map(e=>e.total),this.requestMethodChart.update();const i=s.response_code.map(e=>e.field);this.searchOptions.response_code=[...new Set(this.searchOptions.response_code.concat(i))],this.responseCodeChart.data.labels=i,this.responseCodeChart.data.datasets[0].data=s.response_code.map(e=>e.total),this.responseCodeChart.update();const n=s.component.map(e=>e.field);this.searchOptions.component=[...new Set(this.searchOptions.component.concat(n))],this.componentUseChart.data.labels=n,this.componentUseChart.data.datasets[0].data=s.component.map(e=>e.total),this.componentUseChart.update(),this.longDurationChart.data.labels=s.long_duration.map(e=>e.field),this.longDurationChart.data.datasets[0].data=s.long_duration.map(e=>e.total),this.longDurationChart.update()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}},async searchAuditBy(e,t){e&&(this.searchData[e]=t),this.auditTable.filter=Object.entries(this.searchData).reduce((e,[t,s])=>s?(e[t]=s,e):e,{}),await this.fetchAudit()},showDetailsDialog(e){const t=JSON.parse(e?.request_details||"");try{t.body&&(t.body=JSON.parse(t.body))}catch(e){}this.auditDetailsDialog.data=JSON.stringify(t,null,4),this.auditDetailsDialog.show=!0},shortify:e=>(valueLength=(e||"").length,valueLength<=10?e:`${e.substring(0,5)}...${e.substring(valueLength-5,valueLength)}`),async initCharts(){this.chartsReady?(this.responseCodeChart=new Chart(this.$refs.responseCodeChart.getContext("2d"),{type:"doughnut",options:{responsive:!0,plugins:{legend:{position:"bottom"},title:{display:!1,text:"HTTP Response Codes"}},onClick:(e,t,s)=>{if(t[0]){const e=t[0].index;this.searchAuditBy("response_code",s.data.labels[e])}}},data:{datasets:[{label:"",data:[20,10],backgroundColor:["rgb(100, 99, 200)","rgb(54, 162, 235)","rgb(255, 205, 86)","rgb(255, 5, 86)","rgb(25, 205, 86)","rgb(255, 205, 250)"]}],labels:[]}}),this.requestMethodChart=new Chart(this.$refs.requestMethodChart.getContext("2d"),{type:"bar",options:{responsive:!0,maintainAspectRatio:!1,plugins:{title:{display:!1}},onClick:(e,t,s)=>{if(t[0]){const e=t[0].index;this.searchAuditBy("request_method",s.data.labels[e])}}},data:{datasets:[{label:"",data:[],backgroundColor:["rgb(255, 99, 132)","rgb(54, 162, 235)","rgb(255, 205, 86)","rgb(255, 5, 86)","rgb(25, 205, 86)","rgb(255, 205, 250)"],hoverOffset:4}]}}),this.componentUseChart=new Chart(this.$refs.componentUseChart.getContext("2d"),{type:"pie",options:{responsive:!0,plugins:{legend:{position:"xxx"},title:{display:!1,text:"Components"}},onClick:(e,t,s)=>{if(t[0]){const e=t[0].index;this.searchAuditBy("component",s.data.labels[e])}}},data:{datasets:[{data:[],backgroundColor:["rgb(255, 99, 132)","rgb(54, 162, 235)","rgb(255, 205, 86)","rgb(255, 5, 86)","rgb(25, 205, 86)","rgb(255, 205, 250)","rgb(100, 205, 250)","rgb(120, 205, 250)","rgb(140, 205, 250)","rgb(160, 205, 250)"],hoverOffset:4}]}}),this.longDurationChart=new Chart(this.$refs.longDurationChart.getContext("2d"),{type:"bar",options:{responsive:!0,indexAxis:"y",maintainAspectRatio:!1,plugins:{legend:{title:{display:!1,text:"Long Duration"}}},onClick:(e,t,s)=>{if(t[0]){const e=t[0].index;this.searchAuditBy("path",s.data.labels[e])}}},data:{datasets:[{label:"",data:[],backgroundColor:["rgb(255, 99, 132)","rgb(54, 162, 235)","rgb(255, 205, 86)","rgb(255, 5, 86)","rgb(25, 205, 86)","rgb(255, 205, 250)","rgb(100, 205, 250)","rgb(120, 205, 250)","rgb(140, 205, 250)","rgb(160, 205, 250)"],hoverOffset:4}]}})):console.warn("Charts are not ready yet. Initialization delayed.")}}},window.PageWallet={template:"#page-wallet",data:()=>({parse:{show:!1,invoice:null,lnurlpay:null,lnurlauth:null,sending:!1,data:{request:"",amount:0,comment:"",internalMemo:null,unit:"sat"},paymentChecker:null,copy:{show:!1},camera:{show:!1,camera:"auto"}},receive:{show:!1,status:"pending",paymentReq:null,paymentHash:null,amountMsat:null,minMax:[0,21e14],lnurl:null,units:[],unit:"sat",fiatProvider:"",data:{amount:null,memo:"",internalMemo:null,payment_hash:null}},update:{name:null,currency:null},hasNfc:!1,nfcReaderAbortController:null,formattedFiatAmount:0,paymentFilter:{"status[ne]":"failed"},chartConfig:Quasar.LocalStorage.getItem("lnbits.wallets.chartConfig")||{showPaymentInOutChart:!0,showBalanceChart:!0,showBalanceInOutChart:!0}}),computed:{canPay(){return!!this.parse.invoice&&(this.parse.invoice.expired?(Quasar.Notify.create({message:"Invoice has expired",color:"negative"}),!1):this.parse.invoice.sat<=this.g.wallet.sat)},formattedAmount(){return"sat"==this.receive.unit&&this.g.isSatsDenomination?LNbits.utils.formatMsat(this.receive.amountMsat)+" sat":LNbits.utils.formatCurrency(Number(this.receive.data.amount).toFixed(2),this.g.isSatsDenomination?this.receive.unit:this.g.denomination)},formattedSatAmount(){return LNbits.utils.formatMsat(this.receive.amountMsat)+" sat"}},methods:{handleSendLnurl(e){this.parse.data.request=e,this.parse.show=!0,this.lnurlScan()},msatoshiFormat:e=>LNbits.utils.formatSat(e/1e3),showReceiveDialog(){this.receive.show=!0,this.receive.status="pending",this.receive.paymentReq=null,this.receive.paymentHash=null,this.receive.data.amount=null,this.receive.data.memo=null,this.receive.data.internalMemo=null,this.receive.data.payment_hash=null,this.receive.units=["sat",...this.g.allowedCurrencies.length>0?this.g.allowedCurrencies:this.g.currencies],this.receive.unit=this.g.isFiatPriority&&this.g.wallet.currency||"sat",this.receive.minMax=[0,21e14],this.receive.lnurl=null},onReceiveDialogHide(){this.hasNfc&&this.nfcReaderAbortController.abort()},showParseDialog(){this.parse.show=!0,this.parse.invoice=null,this.parse.lnurlpay=null,this.parse.lnurlauth=null,this.parse.copy.show=window.isSecureContext&&void 0!==navigator.clipboard?.readText,this.parse.data.request="",this.parse.data.comment="",this.parse.data.internalMemo=null,this.parse.sending=!1,this.parse.data.paymentChecker=null,this.parse.camera.show=!1},closeParseDialog(){setTimeout(()=>{clearInterval(this.parse.paymentChecker)},1e4)},handleBalanceUpdate(e){this.g.wallet.sat=this.g.wallet.sat+e},createInvoice(){this.receive.status="loading",this.g.isSatsDenomination||(this.receive.data.amount=100*this.receive.data.amount),LNbits.api.createInvoice(this.g.wallet,this.receive.data.amount,this.receive.data.memo,this.receive.unit,this.receive.lnurlWithdraw,this.receive.fiatProvider,this.receive.data.internalMemo,this.receive.data.payment_hash).then(e=>{if(this.g.updatePayments=!this.g.updatePayments,this.receive.status="success",this.receive.paymentReq=e.data.bolt11,this.receive.fiatPaymentReq=e.data.extra?.fiat_payment_request,this.receive.amountMsat=e.data.amount,this.receive.paymentHash=e.data.payment_hash,this.receive.lnurl||this.readNfcTag(),this.receive.lnurl&&null!==e.data.extra?.lnurl_response){!1===e.data.extra.lnurl_response&&(e.data.extra.lnurl_response="Unable to connect");const t=this.receive.lnurl.callback.split("/")[2];if("string"==typeof e.data.extra.lnurl_response)return void Quasar.Notify.create({timeout:5e3,type:"warning",message:`${t} lnurl-withdraw call failed.`,caption:e.data.extra.lnurl_response});!0===e.data.extra.lnurl_response&&Quasar.Notify.create({timeout:3e3,message:`Invoice sent to ${t}!`,spinner:!0})}}).catch(e=>{LNbits.utils.notifyApiError(e),this.receive.status="pending"})},lnurlScan(){LNbits.api.request("POST","/api/v1/lnurlscan",this.g.wallet.adminkey,{lnurl:this.parse.data.request}).then(e=>{const t=e.data;if("ERROR"!==t.status){if("payRequest"===t.tag)this.parse.lnurlpay=Object.freeze(t),this.parse.data.amount=t.minSendable/1e3,this.receive.units=["sats",...this.g.allowedCurrencies.length>0?this.g.allowedCurrencies:this.g.currencies];else if("login"===t.tag)this.parse.lnurlauth=Object.freeze(t);else if("withdrawRequest"===t.tag){this.parse.show=!1,this.receive.show=!0,this.receive.lnurlWithdraw=Object.freeze(t),this.receive.status="pending",this.receive.paymentReq=null,this.receive.paymentHash=null,this.receive.data.amount=t.maxWithdrawable/1e3,this.receive.data.memo=t.defaultDescription,this.receive.minMax=[t.minWithdrawable/1e3,t.maxWithdrawable/1e3];const e=t.callback.split("/")[2];this.receive.lnurl={domain:e,callback:t.callback,fixed:t.fixed}}}else Quasar.Notify.create({timeout:5e3,type:"warning",message:"lnurl scan failed.",caption:t.reason})}).catch(e=>{LNbits.utils.notifyApiError(e)})},decodeQR(e){this.parse.data.request=e,this.decodeRequest(),this.parse.camera.show=!1},isLnurl:e=>e.toLowerCase().startsWith("lnurl1")||e.startsWith("lnurlp://")||e.startsWith("lnurlw://")||e.startsWith("lnurlauth://")||e.match(/[\w.+-~_]+@[\w.+-~_]/),decodeRequest(){this.parse.show=!0,this.parse.data.request=this.parse.data.request.trim();const e=this.parse.data.request.toLowerCase();if(e.startsWith("lightning:")?this.parse.data.request=this.parse.data.request.slice(10):e.startsWith("lnurl:")?this.parse.data.request=this.parse.data.request.slice(6):e.includes("lightning=lnurl1")&&(this.parse.data.request=this.parse.data.request.split("lightning=")[1].split("&")[0]),this.isLnurl(this.parse.data.request))return void this.lnurlScan();let t;this.parse.data.request.toLowerCase().includes("lightning")&&(this.parse.data.request=this.parse.data.request.split("lightning=")[1],this.parse.data.request.includes("&")&&(this.parse.data.request=this.parse.data.request.split("&")[0]));try{t=decode(this.parse.data.request)}catch(e){return Quasar.Notify.create({timeout:3e3,type:"warning",message:e+".",caption:"400 BAD REQUEST"}),void(this.parse.show=!1)}let s={msat:t.human_readable_part.amount,sat:t.human_readable_part.amount/1e3,fsat:LNbits.utils.formatSat(t.human_readable_part.amount/1e3),bolt11:this.parse.data.request};_.each(t.data.tags,e=>{if(_.isObject(e)&&_.has(e,"description"))if("payment_hash"===e.description)s.hash=e.value;else if("description"===e.description)s.description=e.value;else if("expiry"===e.description){const a=new Date(1e3*(t.data.time_stamp+e.value)),i=new Date(1e3*t.data.time_stamp);s.expireDate=Quasar.date.formatDate(a,"YYYY-MM-DDTHH:mm:ss.SSSZ"),s.createdDate=Quasar.date.formatDate(i,"YYYY-MM-DDTHH:mm:ss.SSSZ"),s.expireDateFrom=moment.utc(a).local().fromNow(),s.createdDateFrom=moment.utc(i).local().fromNow(),s.expired=!1}}),this.g.wallet.currency&&(s.fiatAmount=LNbits.utils.formatCurrency((s.sat/1e8*this.g.exchangeRate).toFixed(2),this.g.wallet.currency)),this.parse.invoice=Object.freeze(s)},payInvoice(){if(this.parse.sending)return;this.parse.sending=!0;const e=Quasar.Notify.create({timeout:0,message:this.$t("payment_processing")});LNbits.api.payInvoice(this.g.wallet,this.parse.data.request,this.parse.data.internalMemo).then(t=>{this.parse.sending=!1,e(),this.g.updatePayments=!this.g.updatePayments,this.parse.show=!1,"success"==t.data.status&&Quasar.Notify.create({type:"positive",message:this.$t("payment_successful")}),"pending"==t.data.status&&Quasar.Notify.create({type:"info",message:this.$t("payment_pending")})}).catch(t=>{this.parse.sending=!1,e(),LNbits.utils.notifyApiError(t),this.g.updatePayments=!this.g.updatePayments})},payLnurl(){this.parse.sending||(this.parse.sending=!0,LNbits.api.request("post","/api/v1/payments/lnurl",this.g.wallet.adminkey,{res:this.parse.lnurlpay,lnurl:this.parse.data.request,unit:this.parse.data.unit,amount:1e3*this.parse.data.amount,comment:this.parse.data.comment,internalMemo:this.parse.data.internalMemo}).then(e=>{if(this.parse.sending=!1,this.parse.show=!1,e.data.extra.success_action){const t=JSON.parse(e.data.extra.success_action);switch(t.tag){case"url":Quasar.Notify.create({message:t.url,caption:t.description,html:!1,type:"positive",timeout:0,closeBtn:!0,actions:[{label:"Open link",color:"white",handler:()=>this.utils.openUrlInNewTab(t.url)}]});break;case"message":Quasar.Notify.create({message:t.message,type:"positive",timeout:0,closeBtn:!0});break;case"aes":this.utils.decryptLnurlPayAES(t,e.data.preimage).then(e=>{Quasar.Notify.create({message:e,caption:t.description,html:!1,type:"positive",timeout:0,closeBtn:!0})}).catch(e=>{Quasar.Notify.create({message:t.description||"Payment successful.",caption:"Could not decrypt success action.",html:!1,type:"warning",timeout:0,closeBtn:!0})})}}}).catch(e=>{this.parse.sending=!1,LNbits.utils.notifyApiError(e)}))},authLnurl(){const e=Quasar.Notify.create({timeout:10,message:"Performing authentication..."});LNbits.api.request("post","/api/v1/lnurlauth",wallet.adminkey,this.parse.lnurlauth).then(t=>{e(),Quasar.Notify.create({message:"Authentication successful.",type:"positive",timeout:3500}),this.parse.show=!1}).catch(e=>{e.response.data.reason?Quasar.Notify.create({message:`Authentication failed. ${this.parse.lnurlauth.callback} says:`,caption:e.response.data.reason,type:"warning",timeout:5e3}):LNbits.utils.notifyApiError(e)})},updateWallet(e){LNbits.api.request("PATCH","/api/v1/wallet",this.g.wallet.adminkey,e).then(e=>{this.g.wallet={...this.g.wallet,...e.data};const t=this.g.user.wallets.findIndex(t=>t.id===e.data.id);-1!==t&&(this.g.user.wallets[t]={...this.g.user.wallets[t],...e.data}),Quasar.Notify.create({message:"Wallet updated.",type:"positive",timeout:3500})}).catch(e=>{LNbits.utils.notifyApiError(e)})},pasteToTextArea(){this.$refs.textArea.focus(),navigator.clipboard.readText().then(e=>{this.parse.data.request=e.trim()})},readNfcTag(){try{if("undefined"==typeof NDEFReader)return void console.debug("NFC not supported on this device or browser.");const e=new NDEFReader;this.nfcReaderAbortController=new AbortController,this.nfcReaderAbortController.signal.onabort=e=>{console.debug("All NFC Read operations have been aborted.")},this.hasNfc=!0;const t=Quasar.Notify.create({message:"Tap your NFC tag to pay this invoice with LNURLw."});return e.scan({signal:this.nfcReaderAbortController.signal}).then(()=>{e.onreadingerror=()=>{Quasar.Notify.create({type:"negative",message:"There was an error reading this NFC tag."})},e.onreading=({message:e})=>{const s=new TextDecoder("utf-8"),a=e.records.find(e=>-1!==s.decode(e.data).toUpperCase().indexOf("LNURLW"));if(a){t(),Quasar.Notify.create({type:"positive",message:"NFC tag read successfully."});const e=s.decode(a.data);this.payInvoiceWithNfc(e)}else Quasar.Notify.create({type:"warning",message:"NFC tag does not have LNURLw record."})}})}catch(e){Quasar.Notify.create({type:"negative",message:e?e.toString():"An unexpected error has occurred."})}},payInvoiceWithNfc(e){const t=Quasar.Notify.create({timeout:0,spinner:!0,message:this.$t("processing_payment")});LNbits.api.request("POST",`/api/v1/payments/${this.receive.paymentReq}/pay-with-nfc`,this.g.wallet.adminkey,{lnurl_w:e}).then(e=>{t(),e.data.success?Quasar.Notify.create({type:"positive",message:"Payment successful"}):Quasar.Notify.create({type:"negative",message:e.data.detail||"Payment failed"})}).catch(e=>{t(),LNbits.utils.notifyApiError(e)})}},created(){const e=new URLSearchParams(window.location.search);(e.has("lightning")||e.has("lnurl"))&&(this.parse.data.request=e.get("lightning")||e.get("lnurl"),this.decodeRequest(),this.parse.show=!0);const t=this.g.user.wallets.find(e=>e.id===this.$route.params.id);t?(this.g.wallet=t,this.g.lastActiveWallet=t.id,this.$q.localStorage.setItem("lnbits.lastActiveWallet",t.id),this.$router.replace(`/wallet/${t.id}`)):(this.g.errorCode=404,this.g.errorMessage="Wallet not found.",this.$router.push("/error"))},watch:{"g.updatePaymentsHash"(){this.receive.show=!1},"g.updatePayments"(){this.parse.show=!1,this.g.wallet.currency&&this.$q.localStorage.getItem("lnbits.exchangeRate."+this.g.wallet.currency)&&(this.g.exchangeRate=this.$q.localStorage.getItem("lnbits.exchangeRate."+this.g.wallet.currency),this.g.fiatBalance=this.g.exchangeRate/1e8*this.g.wallet.sat)},"g.wallet"(){this.g.wallet.currency?(this.g.fiatTracking=!0,this.g.fiatBalance=this.g.exchangeRate/1e8*this.g.wallet.sat):(this.g.fiatBalance=0,this.g.fiatTracking=!1)},"g.isFiatPriority"(){this.receive.unit=this.g.isFiatPriority?this.g.wallet.currency:"sat"},"g.fiatBalance"(){this.formattedFiatAmount=LNbits.utils.formatCurrency(this.g.fiatBalance.toFixed(2),this.g.wallet.currency)},"g.exchangeRate"(){this.g.fiatTracking&&this.g.wallet.currency&&(this.g.fiatBalance=this.g.exchangeRate/1e8*this.g.wallet.sat)}}},window.PageWallets={template:"#page-wallets",data:()=>({user:null,tab:"wallets",wallets:[],addWalletDialog:{show:!1},walletsTable:{columns:[{name:"name",align:"left",label:"Name",field:"name",sortable:!0},{name:"currency",align:"center",label:"Currency",field:"currency",sortable:!0},{name:"updated_at",align:"right",label:"Last Updated",field:"updated_at",sortable:!0}],pagination:{sortBy:"updated_at",rowsPerPage:12,page:1,descending:!0,rowsNumber:10},search:"",hideEmpty:!0,loading:!1}}),watch:{"walletsTable.search":{handler(){const e={};this.walletsTable.search&&(e.search=this.walletsTable.search),this.getUserWallets()}}},methods:{async getUserWallets(e){try{this.walletsTable.loading=!0;const t=LNbits.utils.prepareFilterQuery(this.walletsTable,e),{data:s}=await LNbits.api.request("GET",`/api/v1/wallet/paginated?${t}`,null);this.wallets=s.data,this.walletsTable.pagination.rowsNumber=s.total}catch(e){LNbits.utils.notifyApiError(e)}finally{this.walletsTable.loading=!1}},goToWallet(e){this.$router.push({path:"/wallet",query:{wal:e}})},formattedFiatAmount:(e,t)=>LNbits.utils.formatCurrency(Number(e).toFixed(2),t),formattedSatAmount:e=>LNbits.utils.formatMsat(e)+" sat"},async created(){await this.getUserWallets()}},window.PageUsers={template:"#page-users",data(){return{paymentsWallet:{},cancel:{},users:[],wallets:[],searchData:{user:"",username:"",email:"",pubkey:""},paymentPage:{show:!1},activeWallet:{userId:null,show:!1},activeUser:{data:null,showUserId:!1,show:!1},createWalletDialog:{data:{},show:!1},walletTable:{columns:[{name:"name",align:"left",label:"Name",field:"name"},{name:"id",align:"left",label:"Wallet Id",field:"id"},{name:"currency",align:"left",label:"Currency",field:"currency"},{name:"balance_msat",align:"left",label:"Balance",field:"balance_msat"}],pagination:{sortBy:"name",rowsPerPage:10,page:1,descending:!0,rowsNumber:10},search:null,hideEmpty:!0,loading:!1},usersTable:{columns:[{name:"activated",align:"left",label:this.$t("activated"),field:"activated",sortable:!1},{name:"wallet_id",align:"left",label:"Wallets",field:"wallet_id",sortable:!1},{name:"id",align:"left",label:"User Id",field:"id",sortable:!1},{name:"username",align:"left",label:"Username",field:"username",sortable:!1},{name:"email",align:"left",label:"Email",field:"email",sortable:!1},{name:"pubkey",align:"left",label:"Public Key",field:"pubkey",sortable:!1},{name:"balance_msat",align:"left",label:"Balance",field:"balance_msat",sortable:!1},{name:"transaction_count",align:"left",label:"Payments",field:"transaction_count",sortable:!1},{name:"last_payment",align:"left",label:"Last Payment",field:"last_payment",sortable:!1}],pagination:{sortBy:"created_at",rowsPerPage:10,page:1,descending:!0,rowsNumber:10},sortFields:[{name:"id",label:"User ID"},{name:"username",label:"Username"},{name:"email",label:"Email"},{name:"pubkey",label:"Public Key"},{name:"created_at",label:"Creation Date"},{name:"updated_at",label:"Last Updated"}],search:null,hideEmpty:!0,loading:!1}}},watch:{"usersTable.hideEmpty":function(e,t){this.usersTable.filter=e?{"transaction_count[gt]":0}:{},this.fetchUsers()}},created(){this.fetchUsers()},methods:{formatSat:e=>LNbits.utils.formatSat(Math.floor(e/1e3)),backToUsersPage(){this.activeUser.show=!1,this.paymentPage.show=!1,this.activeWallet.show=!1,this.fetchUsers()},handleBalanceUpdate(){this.fetchWallets(this.activeWallet.userId)},resetPassword(e){return LNbits.api.request("PUT",`/users/api/v1/user/${e}/reset_password`).then(e=>{LNbits.utils.confirmDialog(this.$t("reset_key_generated")+" "+this.$t("reset_key_copy")).onOk(()=>{const t=window.location.origin+"?reset_key="+e.data;this.utils.copyText(t)})}).catch(LNbits.utils.notifyApiError)},sortByColumn(e){this.usersTable.pagination.sortBy===e?this.usersTable.pagination.descending=!this.usersTable.pagination.descending:(this.usersTable.pagination.sortBy=e,this.usersTable.pagination.descending=!1),this.fetchUsers()},createUser(){LNbits.api.request("POST","/users/api/v1/user",null,this.activeUser.data).then(e=>{Quasar.Notify.create({type:"positive",message:"User created!",icon:null}),this.activeUser.setPassword=!0,this.activeUser.data=e.data,this.fetchUsers()}).catch(LNbits.utils.notifyApiError)},updateUser(){LNbits.api.request("PUT",`/users/api/v1/user/${this.activeUser.data.id}`,null,this.activeUser.data).then(()=>{Quasar.Notify.create({type:"positive",message:"User updated!",icon:null}),this.activeUser.data=null,this.activeUser.show=!1,this.fetchUsers()}).catch(LNbits.utils.notifyApiError)},createWallet(){const e=this.activeWallet.userId;e?LNbits.api.request("POST",`/users/api/v1/user/${e}/wallet`,null,this.createWalletDialog.data).then(()=>{this.fetchWallets(e),Quasar.Notify.create({type:"positive",message:"Wallet created!"})}).catch(LNbits.utils.notifyApiError):Quasar.Notify.create({type:"warning",message:"No user selected!",icon:null})},deleteUser(e){LNbits.utils.confirmDialog("Are you sure you want to delete this user?").onOk(()=>{LNbits.api.request("DELETE",`/users/api/v1/user/${e}`).then(()=>{this.fetchUsers(),Quasar.Notify.create({type:"positive",message:"User deleted!",icon:null}),this.activeUser.data=null,this.activeUser.show=!1}).catch(LNbits.utils.notifyApiError)})},undeleteUserWallet(e,t){LNbits.api.request("PUT",`/users/api/v1/user/${e}/wallet/${t}/undelete`).then(()=>{this.fetchWallets(e),Quasar.Notify.create({type:"positive",message:"Undeleted user wallet!",icon:null})}).catch(LNbits.utils.notifyApiError)},deleteUserWallet(e,t,s){const a=s?"Wallet is already deleted, are you sure you want to permanently delete this user wallet?":"Are you sure you want to delete this user wallet?";LNbits.utils.confirmDialog(a).onOk(()=>{LNbits.api.request("DELETE",`/users/api/v1/user/${e}/wallet/${t}`).then(()=>{this.fetchWallets(e),Quasar.Notify.create({type:"positive",message:"User wallet deleted!",icon:null})}).catch(LNbits.utils.notifyApiError)})},deleteAllUserWallets(e){LNbits.utils.confirmDialog(this.$t("confirm_delete_all_wallets")).onOk(()=>{LNbits.api.request("DELETE",`/users/api/v1/user/${e}/wallets`).then(t=>{Quasar.Notify.create({type:"positive",message:t.data.message,icon:null}),this.fetchWallets(e)}).catch(LNbits.utils.notifyApiError)})},copyWalletLink(e){const t=`${window.location.origin}/wallet?usr=${this.activeWallet.userId}&wal=${e}`;this.utils.copyText(t)},fetchUsers(e){this.relaxFilterForFields(["username","email"]);const t=LNbits.utils.prepareFilterQuery(this.usersTable,e);LNbits.api.request("GET",`/users/api/v1/user?${t}`).then(e=>{this.usersTable.loading=!1,this.usersTable.pagination.rowsNumber=e.data.total,this.users=e.data.data}).catch(LNbits.utils.notifyApiError)},fetchWallets(e){return LNbits.api.request("GET",`/users/api/v1/user/${e}/wallet`).then(t=>{this.wallets=t.data,this.activeWallet.userId=e,this.activeWallet.show=!0}).catch(LNbits.utils.notifyApiError)},relaxFilterForFields(e=[]){e.forEach(e=>{const t=this.usersTable?.filter?.[e];t&&this.usersTable.filter[e]&&(this.usersTable.filter[`${e}[like]`]=t,delete this.usersTable.filter[e])})},updateWallet(e){LNbits.api.request("PATCH","/api/v1/wallet",e.adminkey,{name:e.name}).then(()=>{e.editable=!1,Quasar.Notify.create({message:"Wallet name updated.",type:"positive",timeout:3500})}).catch(e=>{LNbits.utils.notifyApiError(e)})},toggleAdmin(e){LNbits.api.request("PUT",`/users/api/v1/user/${e}/admin`).then(()=>{this.fetchUsers(),Quasar.Notify.create({type:"positive",message:"Toggled admin!",icon:null})}).catch(LNbits.utils.notifyApiError)},toggleUserActivated(e){LNbits.api.request("PUT",`/users/api/v1/user/${e}/activate`).then(e=>{this.fetchUsers(),Quasar.Notify.create({type:"positive",message:e.data.message,icon:null})}).catch(LNbits.utils.notifyApiError)},async showAccountPage(e){if(this.activeUser.showPassword=!1,this.activeUser.showUserId=!1,this.activeUser.setPassword=!1,!e)return this.activeUser.data={extra:{}},void(this.activeUser.show=!0);try{const{data:t}=await LNbits.api.request("GET",`/users/api/v1/user/${e}`);this.activeUser.data=t,this.activeUser.show=!0}catch(e){console.warn(e),Quasar.Notify.create({type:"warning",message:"Failed to get user!"}),this.activeUser.show=!1}},async impersonateUser(e){try{await LNbits.api.impersonateUser(e),LNbits.utils.backupLocalStorage("impersonation",!0),this.$q.localStorage.setItem("lnbits.disclaimerShown",!0),window.location="/wallet"}catch(e){console.warn(e),Quasar.Notify.create({type:"warning",message:"Failed to impersonate user!"})}},async showWalletPayments(e){this.activeUser.show=!1,await this.fetchWallets(this.users[0].id),await this.showPayments(e)},showPayments(e){this.paymentsWallet=this.wallets.find(t=>t.id===e),this.paymentPage.show=!0},searchUserBy(e){const t=this.searchData[e];this.usersTable.filter={},t&&(this.usersTable.filter[e]=t),this.fetchUsers()},shortify:e=>(valueLength=(e||"").length,valueLength<=10?e:`${e.substring(0,5)}...${e.substring(valueLength-5,valueLength)}`)}},window.PageAccount={template:"#page-account",data(){return{untouchedUser:null,hasUsername:!1,showUserId:!1,themeOptions:[{name:"bitcoin",color:"deep-orange"},{name:"classic",color:"purple"},{name:"mint",color:"green"},{name:"autumn",color:"brown"},{name:"monochrome",color:"grey"},{name:"salvador",color:"blue-10"},{name:"freedom",color:"pink-13"},{name:"cyber",color:"light-green-9"},{name:"flamingo",color:"pink-3"}],defaultSiteCustomisation:{locale:"en"},reactionOptions:["None","confettiBothSides","confettiFireworks","confettiStars","confettiTop"],borderOptions:["retro-border","hard-border","neon-border","no-border"],tab:"user",credentialsData:{show:!1,oldPassword:null,newPassword:null,newPasswordRepeat:null,username:null,pubkey:null},apiAcl:{showNewAclDialog:!1,showPasswordDialog:!1,showNewTokenDialog:!1,data:[],passwordGuardedFunction:null,newAclName:"",newTokenName:"",password:"",apiToken:null,selectedTokenId:null,columns:[{name:"Name",align:"left",label:this.$t("Name"),field:"Name",sortable:!1},{name:"path",align:"left",label:this.$t("path"),field:"path",sortable:!1},{name:"read",align:"left",label:this.$t("read"),field:"read",sortable:!1},{name:"write",align:"left",label:this.$t("write"),field:"write",sortable:!1}],pagination:{rowsPerPage:100,page:1}},selectedApiAcl:{id:null,name:null,endpoints:[],token_id_list:[],allRead:!1,allWrite:!1},assets:[],assetsTable:{loading:!1,columns:[{name:"name",align:"left",label:this.$t("Name"),field:"name",sortable:!0},{name:"created_at",align:"left",label:this.$t("created_at"),field:"created_at",sortable:!0}],pagination:{rowsPerPage:6,page:1}},assetsUploadToPublic:!1,notifications:{nostr:{identifier:""}},labels:[],labelsDialog:{show:!1,data:{name:"",description:"",color:"#000000"}},labelsTable:{loading:!1,columns:[{name:"actions",align:"left"},{name:"name",align:"left",label:this.$t("Name"),field:"name",sortable:!0},{name:"description",align:"left",label:this.$t("description"),field:"description"},{name:"color",align:"left",label:this.$t("color"),field:"color"}],pagination:{rowsPerPage:6,page:1}}}},watch:{tab(e){this.$router.push(`/account#${e}`)},$route(e){e.hash.length>1&&(this.tab=e.hash.replace("#",""))},"assetsTable.search":{handler(){const e={};this.assetsTable.search&&(e.search=this.assetsTable.search),this.getUserAssets()}}},computed:{isUserTouched(){return!_.isEqual(this.g.user,this.untouchedUser)}},methods:{changeLanguage(e){window.i18n.global.locale=e,this.$q.localStorage.set("lnbits.lang",e)},async updateAccount(){try{const{data:e}=await LNbits.api.request("PATCH","/api/v1/auth",null,{user_id:this.g.user.id,username:this.g.user.username,email:this.g.user.email,extra:this.g.user.extra});this.untouchedUser=JSON.parse(JSON.stringify(this.g.user)),this.hasUsername=!!e.username,Quasar.Notify.create({type:"positive",message:"Account updated."})}catch(e){LNbits.utils.notifyApiError(e)}},disableUpdatePassword(){return!this.credentialsData.newPassword||!this.credentialsData.newPasswordRepeat||this.credentialsData.newPassword!==this.credentialsData.newPasswordRepeat},async updatePassword(){if(this.credentialsData.username)try{const{data:e}=await LNbits.api.request("PUT","/api/v1/auth/password",null,{user_id:this.g.user.id,username:this.credentialsData.username,password_old:this.credentialsData.oldPassword,password:this.credentialsData.newPassword,password_repeat:this.credentialsData.newPasswordRepeat});this.untouchedUser=JSON.parse(JSON.stringify(e)),this.hasUsername=!!e.username,this.credentialsData.show=!1,Quasar.Notify.create({type:"positive",message:"Password updated."})}catch(e){LNbits.utils.notifyApiError(e)}else Quasar.Notify.create({type:"warning",message:"Please set a username."})},async updatePubkey(){try{const{data:e}=await LNbits.api.request("PUT","/api/v1/auth/pubkey",null,{user_id:this.g.user.id,pubkey:this.credentialsData.pubkey});this.untouchedUser=JSON.parse(JSON.stringify(e)),this.hasUsername=!!e.username,this.credentialsData.show=!1,this.$q.notify({type:"positive",message:"Public key updated."})}catch(e){LNbits.utils.notifyApiError(e)}},showUpdateCredentials(){this.credentialsData={show:!0,oldPassword:null,username:this.g.user.username,pubkey:this.g.user.pubkey,newPassword:null,newPasswordRepeat:null}},newApiAclDialog(){this.apiAcl.newAclName=null,this.apiAcl.showNewAclDialog=!0},newTokenAclDialog(){this.apiAcl.newTokenName=null,this.apiAcl.newTokenExpiry=null,this.apiAcl.showNewTokenDialog=!0},handleApiACLSelected(e){this.selectedApiAcl={id:null,name:null,endpoints:[],token_id_list:[]},this.apiAcl.selectedTokenId=null,e&&setTimeout(()=>{const t=this.apiAcl.data.find(t=>t.id===e);this.selectedApiAcl&&(this.selectedApiAcl={...t},this.selectedApiAcl.allRead=this.selectedApiAcl.endpoints.every(e=>e.read),this.selectedApiAcl.allWrite=this.selectedApiAcl.endpoints.every(e=>e.write))})},handleAllEndpointsReadAccess(){this.selectedApiAcl.endpoints.forEach(e=>e.read=this.selectedApiAcl.allRead)},handleAllEndpointsWriteAccess(){this.selectedApiAcl.endpoints.forEach(e=>e.write=this.selectedApiAcl.allWrite)},async getApiACLs(){try{const{data:e}=await LNbits.api.request("GET","/api/v1/auth/acl",null);this.apiAcl.data=e.access_control_list}catch(e){LNbits.utils.notifyApiError(e)}},askPasswordAndRunFunction(e){this.apiAcl.passwordGuardedFunction=e,this.apiAcl.showPasswordDialog=!0},runPasswordGuardedFunction(){this.apiAcl.showPasswordDialog=!1;const e=this.apiAcl.passwordGuardedFunction;e&&this[e]()},async addApiACL(){if(this.apiAcl.newAclName){try{const{data:e}=await LNbits.api.request("PUT","/api/v1/auth/acl",null,{id:this.apiAcl.newAclName,name:this.apiAcl.newAclName,password:this.apiAcl.password});this.apiAcl.data=e.access_control_list;const t=this.apiAcl.data.find(e=>e.name===this.apiAcl.newAclName);this.handleApiACLSelected(t.id),this.apiAcl.showNewAclDialog=!1,this.$q.notify({type:"positive",message:"Access Control List created."})}catch(e){LNbits.utils.notifyApiError(e)}finally{this.apiAcl.name="",this.apiAcl.password=""}this.apiAcl.showNewAclDialog=!1}else this.$q.notify({type:"warning",message:"Name is required."})},async updateApiACLs(){try{const{data:e}=await LNbits.api.request("PUT","/api/v1/auth/acl",null,{id:this.g.user.id,password:this.apiAcl.password,...this.selectedApiAcl});this.apiAcl.data=e.access_control_list}catch(e){LNbits.utils.notifyApiError(e)}finally{this.apiAcl.password=""}},async deleteApiACL(){if(this.selectedApiAcl.id){try{await LNbits.api.request("DELETE","/api/v1/auth/acl",null,{id:this.selectedApiAcl.id,password:this.apiAcl.password}),this.$q.notify({type:"positive",message:"Access Control List deleted."})}catch(e){LNbits.utils.notifyApiError(e)}finally{this.apiAcl.password=""}this.apiAcl.data=this.apiAcl.data.filter(e=>e.id!==this.selectedApiAcl.id),this.handleApiACLSelected(this.apiAcl.data[0]?.id)}},async generateApiToken(){if(!this.selectedApiAcl.id)return;const e=new Date(this.apiAcl.newTokenExpiry)-new Date;try{const{data:t}=await LNbits.api.request("POST","/api/v1/auth/acl/token",null,{acl_id:this.selectedApiAcl.id,token_name:this.apiAcl.newTokenName,password:this.apiAcl.password,expiration_time_minutes:Math.trunc(e/6e4)});this.apiAcl.apiToken=t.api_token,this.apiAcl.selectedTokenId=t.id,Quasar.Notify.create({type:"positive",message:"Token Generated."}),await this.getApiACLs(),this.handleApiACLSelected(this.selectedApiAcl.id),this.apiAcl.showNewTokenDialog=!1}catch(e){LNbits.utils.notifyApiError(e)}finally{this.apiAcl.password=""}},async deleteToken(){if(this.apiAcl.selectedTokenId)try{await LNbits.api.request("DELETE","/api/v1/auth/acl/token",null,{id:this.apiAcl.selectedTokenId,acl_id:this.selectedApiAcl.id,password:this.apiAcl.password}),this.$q.notify({type:"positive",message:"Token deleted."}),this.selectedApiAcl.token_id_list=this.selectedApiAcl.token_id_list.filter(e=>e.id!==this.apiAcl.selectedTokenId),this.apiAcl.selectedTokenId=null}catch(e){LNbits.utils.notifyApiError(e)}finally{this.apiAcl.password=""}},async getUserAssets(e){try{this.assetsTable.loading=!0;const t=LNbits.utils.prepareFilterQuery(this.assetsTable,e),{data:s}=await LNbits.api.request("GET",`/api/v1/assets/paginated?${t}`,null);this.assets=s.data,this.assetsTable.pagination.rowsNumber=s.total}catch(e){LNbits.utils.notifyApiError(e)}finally{this.assetsTable.loading=!1}},onImageInput(e){const t=e.target.files[0];t&&this.uploadAsset(t),e.target.value=null},onBackgroundImageInput(e){const t=e.target.files[0];t&&this.uploadBackgroundImage(t),e.target.value=null},async uploadAsset(e,{isPublic:t=this.assetsUploadToPublic,notifySuccess:s=!0}={}){const a=new FormData;a.append("file",e);try{const{data:e}=await LNbits.api.request("POST",`/api/v1/assets?public_asset=${t}`,null,a,{headers:{"Content-Type":"multipart/form-data"}});return s&&this.$q.notify({type:"positive",message:"Upload successful!",icon:null}),await this.getUserAssets(),e}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}},async uploadBackgroundImage(e){const t=await this.uploadAsset(e,{isPublic:!1,notifySuccess:!1});if(!t)return;const s=`${window.location.origin}/api/v1/assets/${t.id}/thumbnail`;await this.siteCustomisationChanged({bgimageChoice:s})},async deleteAsset(e){LNbits.utils.confirmDialog("Are you sure you want to delete this asset?").onOk(async()=>{try{await LNbits.api.request("DELETE",`/api/v1/assets/${e.id}`,null),this.$q.notify({type:"positive",message:"Asset deleted."}),await this.getUserAssets()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}})},async toggleAssetPublicAccess(e){try{await LNbits.api.request("PUT",`/api/v1/assets/${e.id}`,null,{is_public:!e.is_public}),this.$q.notify({type:"positive",message:"Update successful!",icon:null}),await this.getUserAssets()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}},copyAssetLinkToClipboard(e){const t=`${window.location.origin}/api/v1/assets/${e.id}/data`;this.utils.copyText(t)},addUserLabel(){if(!this.labelsDialog.data.name)return void this.$q.notify({type:"warning",message:"Name is required."});if(!this.labelsDialog.data.color)return void this.$q.notify({type:"warning",message:"Color is required."});this.g.user.extra.labels=this.g.user.extra.labels||[];if(!this.g.user.extra.labels.find(e=>e.name===this.labelsDialog.data.name))return this.g.user.extra.labels.unshift({...this.labelsDialog.data}),this.labelsDialog.show=!1,!0;this.$q.notify({type:"warning",message:"A label with this name already exists."})},openAddLabelDialog(){this.labelsDialog.data={name:"",description:"",color:"#000000"},this.labelsDialog.show=!0},openEditLabelDialog(e){this.labelsDialog.data={name:e.name,description:e.description,color:e.color},this.labelsDialog.show=!0},updateUserLabel(){const e=this.labelsDialog.data,t=JSON.parse(JSON.stringify(this.g.user.extra.labels));this.g.user.extra.labels=this.g.user.extra.labels.filter(t=>t.name!==e.name);this.addUserLabel()||(this.g.user.extra.labels=t),this.labelsDialog.show=!1},deleteUserLabel(e){LNbits.utils.confirmDialog("Are you sure you want to delete this label?").onOk(()=>{this.g.user.extra.labels=this.g.user.extra.labels.filter(t=>t.name!==e.name)})},async siteCustomisationChanged(e={}){try{Object.entries(e||{}).forEach(([e,t])=>{e in this.g&&(this.g[e]=t)}),await LNbits.api.updateUiCustomization(e),this.$q.notify({type:"positive",message:"UI Customization updated."})}catch(e){LNbits.utils.notifyApiError(e)}},resetThemeDefaults(){const e={themeChoice:this.g.settings.defaultTheme,borderChoice:this.g.settings.defaultBorder,gradientChoice:this.g.settings.defaultGradient,bgimageChoice:this.g.settings.defaultBgimage||"",reactionChoice:this.g.settings.defaultReaction,darkChoice:this.g.settings.defaultDark,cardRoundedChoice:this.g.settings.defaultCardRounded,cardGradientChoice:this.g.settings.defaultCardGradient,cardShadowChoice:this.g.settings.defaultCardShadow,burgerMenuChoice:this.g.settings.defaultBurgerMenuBackground};this.siteCustomisationChanged(e)}},async created(){this.untouchedUser=JSON.parse(JSON.stringify(this.g.user)),this.hasUsername=!!this.g.user.username,this.$route.hash.length>1&&(this.tab=this.$route.hash.replace("#","")),await this.getApiACLs(),await this.getUserAssets(),this.themeOptions=this.themeOptions.filter(e=>this.g.settings.themeOptions.includes(e.name))}},window.PageAdmin={template:"#page-admin",data:()=>({tab:"funding",settings:{},formData:{lnbits_exchange_rate_providers:[],lnbits_audit_exclude_paths:[],lnbits_audit_include_paths:[],lnbits_audit_http_response_codes:[]},isSuperUser:!1,needsRestart:!1}),watch:{tab(e){if(["wasm-runtime","wasm-limit-config"].includes(e)&&this.$route.path.startsWith("/admin/extensions/wasm"))return;const t=this.adminRouteForTab(e);this.$route.fullPath!==t&&this.$router.push(t)},$route(e){const t=this.adminTabFromRoute(e);this.tab!==t&&(this.tab=t)}},async created(){this.tab=this.adminTabFromRoute(this.$route),await this.getSettings()},computed:{checkChanges(){return!_.isEqual(this.settings,this.formData)}},methods:{adminTabFromRoute:e=>e.path.startsWith("/admin/extensions/wasm/limits")?"wasm-limit-config":e.path.startsWith("/admin/extensions/wasm")?"wasm-runtime":e.hash.length>1?e.hash.replace("#",""):"funding",adminRouteForTab:e=>"wasm-runtime"===e?"/admin/extensions/wasm":"wasm-limit-config"===e?"/admin/extensions/wasm/limits":`/admin#${e}`,getDefaultSetting(e){LNbits.api.getDefaultSetting(e).then(t=>{this.formData[e]=t.data.default_value})},restartServer(){LNbits.api.request("GET","/admin/api/v1/restart/").then(e=>{this.$q.notify({type:"positive",message:"Success! Restarted Server",icon:null}),this.needsRestart=!1}).catch(LNbits.utils.notifyApiError)},async getSettings(){await LNbits.api.request("GET","/admin/api/v1/settings",this.g.user.wallets[0].adminkey).then(e=>{this.isSuperUser=e.data.is_super_user||!1,this.settings=e.data,this.formData={...this.settings}}).catch(LNbits.utils.notifyApiError)},updateSettings(){const e=_.omit(this.formData,["is_super_user","lnbits_allowed_funding_sources","touch"]);LNbits.api.request("PUT","/admin/api/v1/settings",this.g.user.wallets[0].adminkey,e).then(e=>{this.needsRestart=this.settings.lnbits_backend_wallet_class!==this.formData.lnbits_backend_wallet_class,this.settings=this.formData,this.formData=_.clone(this.settings),Quasar.Notify.create({type:"positive",message:"Success! Settings changed! "+(this.needsRestart?"Restart required!":""),icon:null})}).catch(LNbits.utils.notifyApiError)},deleteSettings(){LNbits.utils.confirmDialog("Are you sure you want to restore settings to default?").onOk(()=>{LNbits.api.request("DELETE","/admin/api/v1/settings").then(e=>{Quasar.Notify.create({type:"positive",message:"Success! Restored settings to defaults. Restarting...",icon:null}),this.$q.localStorage.clear()}).catch(LNbits.utils.notifyApiError)})},downloadBackup(){window.open("/admin/api/v1/backup","_blank")}}},window.app.component("lnbits-admin-funding-seed-backup",{props:["active","is-super-user","form-data","settings"],template:"#lnbits-admin-funding-seed-backup",data:()=>({dialog:{show:!1,step:1,seed:"",visible:!1,challenge:[],answers:{},error:"",confirmField:""}}),watch:{active(e){e&&this.openIfRequired()},"formData.lnbits_backend_wallet_class"(e,t){const s=this.seedBackupSource(e);t&&s&&this.formData[s.seedField]&&(this.formData[s.confirmField]=!1),this.openIfRequired()},"formData.boltz_mnemonic"(){this.formData.boltz_mnemonic_backup_confirmed=this.formData.boltz_mnemonic===this.settings.boltz_mnemonic&&this.settings.boltz_mnemonic_backup_confirmed,this.openIfRequired()},"formData.phoenixd_mnemonic"(){this.formData.phoenixd_mnemonic_backup_confirmed=this.formData.phoenixd_mnemonic===this.settings.phoenixd_mnemonic&&this.settings.phoenixd_mnemonic_backup_confirmed,this.openIfRequired()},"formData.spark_l2_mnemonic"(){this.formData.spark_l2_mnemonic_backup_confirmed=this.formData.spark_l2_mnemonic===this.settings.spark_l2_mnemonic&&this.settings.spark_l2_mnemonic_backup_confirmed,this.openIfRequired()}},computed:{seedWords(){return this.dialog.seed.split(/\s+/).filter(Boolean).map((e,t)=>({index:t,word:e}))}},created(){this.openIfRequired()},methods:{seedBackupSource(e=this.formData.lnbits_backend_wallet_class){return"BoltzWallet"===e?{seedField:"boltz_mnemonic",confirmField:"boltz_mnemonic_backup_confirmed"}:"PhoenixdWallet"===e?{seedField:"phoenixd_mnemonic",confirmField:"phoenixd_mnemonic_backup_confirmed"}:"SparkL2Wallet"===e?{seedField:"spark_l2_mnemonic",confirmField:"spark_l2_mnemonic_backup_confirmed"}:void 0},openIfRequired(){if(!this.active||!this.isSuperUser)return;const e=this.seedBackupSource();if(!e)return;const t=(this.formData[e.seedField]||"").trim(),s=this.formData[e.confirmField];!t||s||this.dialog.show||(this.dialog={show:!0,step:1,seed:t,visible:!1,challenge:[],answers:{},error:"",confirmField:e.confirmField})},prepareChallenge(){const e=this.dialog.seed.split(/\s+/).filter(Boolean),t=Math.min(4,e.length),s=_.shuffle([...Array(e.length).keys()]).slice(0,t);this.dialog.challenge=s.sort((e,t)=>e-t).map(t=>({index:t,word:e[t]})),this.dialog.answers={},this.dialog.error="",this.dialog.step=2},submitChallenge(){if(!this.dialog.challenge.every(({index:e,word:t})=>(this.dialog.answers[e]||"").trim().toLowerCase()===t.toLowerCase()))return void(this.dialog.error="One or more words are incorrect. Check your backup and try again.");const e=this.dialog.confirmField;LNbits.api.request("PATCH","/admin/api/v1/settings",this.g.user.wallets[0].adminkey,{[e]:!0}).then(()=>{this.formData[e]=!0,this.settings[e]=!0,this.dialog.show=!1,Quasar.Notify.create({type:"positive",message:"Seed backup confirmed",icon:"check"})}).catch(LNbits.utils.notifyApiError)}}}),window.app.component("lnbits-admin-funding",{props:["active","is-super-user","form-data","settings"],template:"#lnbits-admin-funding",data:()=>({auditData:[]}),created(){this.getAudit()},methods:{getAudit(){LNbits.api.request("GET","/admin/api/v1/audit",this.g.user.wallets[0].adminkey).then(e=>{this.auditData=e.data}).catch(LNbits.utils.notifyApiError)}}}),window.app.component("lnbits-admin-funding-sources",{template:"#lnbits-admin-funding-sources",props:["form-data","allowed-funding-sources"],methods:{getFundingSourceLabel(e){const t=this.rawFundingSources.find(t=>t[0]===e);return t?t[1]:e},showQRValue(e){this.qrValue=e,this.showQRDialog=!0}},computed:{fundingSources(){let e=[];for(const[t,s,a]of this.rawFundingSources){const s={};if(null!==a)for(let[e,t]of Object.entries(a))s[e]="string"==typeof t?{label:t,value:null}:t||{};e.push([t,s])}return new Map(e)},sortedAllowedFundingSources(){return this.allowedFundingSources.sort()}},data:()=>({hideInput:!0,showQRDialog:!1,qrValue:"",rawFundingSources:[["VoidWallet","Void Wallet",null],["FakeWallet","Fake Wallet",{fake_wallet_secret:"Secret",lnbits_denomination:'"sats" or 3 Letter Custom Denomination'}],["CLNRestWallet","Core Lightning Rest (plugin)",{clnrest_url:"Endpoint",clnrest_ca:"ca.pem",clnrest_cert:"server.pem",clnrest_readonly_rune:"Rune used for readonly requests",clnrest_invoice_rune:"Rune used for creating invoices",clnrest_pay_rune:"Rune used for paying invoices using pay",clnrest_renepay_rune:"Rune used for paying invoices using renepay",clnrest_last_pay_index:"Ignores any invoices paid prior to or including this index. 0 is equivalent to not specifying and negative value is invalid.",clnrest_nodeid:"Node id"}],["CoreLightningWallet","Core Lightning",{corelightning_rpc:"Endpoint",corelightning_pay_command:"Custom Pay Command"}],["CoreLightningRestWallet","Core Lightning Rest (legacy)",{corelightning_rest_url:"Endpoint",corelightning_rest_cert:"Certificate",corelightning_rest_macaroon:"Macaroon"}],["LndRestWallet","Lightning Network Daemon (LND Rest)",{lnd_rest_endpoint:"Endpoint",lnd_rest_cert:"Certificate",lnd_rest_macaroon:"Macaroon",lnd_rest_macaroon_encrypted:"Encrypted Macaroon",lnd_rest_route_hints:{advanced:!0,label:"Enable Route Hints"},lnd_rest_allow_self_payment:{advanced:!0,label:"Allow Self Payment"}}],["LndWallet","Lightning Network Daemon (LND)",{lnd_grpc_endpoint:"Endpoint",lnd_grpc_cert:"Certificate",lnd_grpc_port:"Port",lnd_grpc_macaroon:"GRPC Macaroon",lnd_grpc_invoice_macaroon:"GRPC Invoice Macaroon",lnd_grpc_admin_macaroon:"GRPC Admin Macaroon",lnd_grpc_macaroon_encrypted:"Encrypted Macaroon"}],["LnTipsWallet","LN.Tips",{lntips_api_endpoint:"Endpoint",lntips_api_key:"API Key"}],["LNPayWallet","LN Pay",{lnpay_api_endpoint:"Endpoint",lnpay_api_key:"API Key",lnpay_wallet_key:"Wallet Key"}],["EclairWallet","Eclair (ACINQ)",{eclair_url:"URL",eclair_pass:"Password"}],["LNbitsWallet","LNbits",{lnbits_endpoint:"Endpoint",lnbits_key:"Admin Key"}],["BlinkWallet","Blink",{blink_api_endpoint:"Endpoint",blink_ws_endpoint:"WebSocket",blink_token:"Key"}],["AlbyWallet","Alby",{alby_api_endpoint:"Endpoint",alby_access_token:"Key"}],["BoltzWallet","Boltz",{boltz_client_endpoint:{label:"Boltz client endpoint",value:"127.0.0.1:9002"},boltz_client_macaroon:{label:"Admin Macaroon path or hex",value:"/home/ubuntu/.boltz/macaroons/admin.macaroon"},boltz_client_cert:{label:"Certificate path or hex",value:"/home/ubuntu/.boltz/tls.cert"},boltz_mnemonic:{label:"Liquid seed phrase",hint:"Boltz will fetch once connected, but you can change later (can be opened in a liquid wallet) ",copy:!0,qrcode:!0},boltz_client_password:{label:"Wallet Password (optional)",advanced:!0}}],["ZBDWallet","ZBD",{zbd_api_endpoint:"Endpoint",zbd_api_key:"Key"}],["PhoenixdWallet","Phoenixd",{phoenixd_api_endpoint:"Endpoint",phoenixd_api_password:"Key",phoenixd_data_dir:{label:"Data Directory",hint:"Directory where phoenixd stores its data, including the seed phrase."},phoenixd_mnemonic:{label:"Phoenixd Seed Phrase",hint:"Only available if phoenixd data-dir is specified",readonly:!0,copy:!0,qrcode:!0}}],["OpenNodeWallet","OpenNode",{opennode_api_endpoint:"Endpoint",opennode_key:"Key"}],["ClicheWallet","Cliche (NBD)",{cliche_endpoint:"Endpoint"}],["SparkWallet","Spark",{spark_url:"Endpoint",spark_token:"Token"}],["SparkL2Wallet","Spark (L2)",{spark_l2_external_endpoint:{label:"External Sidecar Endpoint",hint:"Make sure to also specify the API key if your sidecar requires authentication.",value:""},spark_l2_mnemonic:{label:"External Sidecar Mnemonic",hint:"Mnemonic for the Spark wallet on the external sidecar. Required if the side car does not have its own mnemonic.",value:""},spark_l2_external_api_key:{label:"External Sidecar API Key",hint:"API key for authenticating with the external sidecar if it requires authentication.",value:""},spark_l2_network:{label:"Network",value:"MAINNET",hint:"The network to use for the Spark wallet.",advanced:!0},spark_l2_pay_wait_ms:{label:"Payment Wait Time (ms)",hint:"The time to wait for a payment to be processed before considering it failed.",advanced:!0},spark_l2_pay_poll_ms:{label:"Payment Poll Time (ms)",hint:"The time to wait between polling for payment status updates.",advanced:!0},spark_l2_stream_keepalive_ms:{label:"Stream Keepalive Time (ms)",hint:"The time to wait between sending keepalive messages to the Spark sidecar to keep the connection open.",advanced:!0}}],["NWCWallet","Nostr Wallet Connect",{nwc_pairing_url:"Pairing URL"}],["BreezSdkWallet","Breez SDK",{breez_api_key:"Breez API Key",breez_greenlight_seed:"Greenlight Seed",breez_greenlight_device_key:"Greenlight Device Key",breez_greenlight_device_cert:"Greenlight Device Cert",breez_greenlight_invite_code:"Greenlight Invite Code"}],["StrikeWallet","Strike (alpha)",{strike_api_endpoint:"API Endpoint",strike_api_key:"API Key"}],["BreezLiquidSdkWallet","Breez Liquid SDK",{breez_liquid_api_key:"Breez API Key (can be empty)",breez_liquid_seed:"Liquid seed phrase",breez_liquid_fee_offset_sat:"Offset amount in sats to increase fee limit"}]]})}),window.app.component("lnbits-admin-fiat-providers",{props:["form-data"],template:"#lnbits-admin-fiat-providers",data:()=>({formAddStripeUser:"",formAddPaypalUser:"",formAddSquareUser:"",formAddRevolutUser:"",creatingRevolutWebhook:!1,hideInputToggle:!0}),computed:{stripeWebhookUrl(){return this.formData?.stripe_payment_webhook_url||this.calculateWebhookUrl("stripe")},paypalWebhookUrl(){return this.formData?.paypal_payment_webhook_url||this.calculateWebhookUrl("paypal")},revolutWebhookUrl(){return this.formData?.revolut_payment_webhook_url||this.calculateWebhookUrl("revolut")}},watch:{formData:{handler(){this.syncWebhookUrls()},immediate:!0}},methods:{basePathFromLocation(){if("undefined"==typeof window)return"";const e=window.location.pathname.replace(/\/+$/,""),t=e.lastIndexOf("/admin");return(t>=0?e.slice(0,t):e||"")||""},calculateWebhookUrl(e){if("undefined"==typeof window)return"";const t=`${this.basePathFromLocation()}/api/v1/callback/${e}`.replace(/\/+/g,"/"),s=t.startsWith("/")?t:`/${t}`;return`${window.location.origin}${s}`},syncWebhookUrls(){this.maybeSetWebhookUrl("stripe_payment_webhook_url","stripe"),this.maybeSetWebhookUrl("paypal_payment_webhook_url","paypal"),this.maybeSetWebhookUrl("square_payment_webhook_url","square"),this.maybeSetWebhookUrl("revolut_payment_webhook_url","revolut")},maybeSetWebhookUrl(e,t){if(!this.formData)return;const s=this.calculateWebhookUrl(t),a=this.formData[e];(!a||a.includes("your-lnbits-domain-here.com"))&&s&&(this.formData[e]=s)},copyWebhookUrl(e){e&&this.copyText(e)},isClearnetWebhookUrl(e){let t;try{t=new URL(e)}catch(e){return!1}const s=t.hostname.toLowerCase();return!!["http:","https:"].includes(t.protocol)&&(!("localhost"===s||s.endsWith(".localhost")||s.endsWith(".local")||s.endsWith(".onion"))&&!(/^127\./.test(s)||/^10\./.test(s)||/^192\.168\./.test(s)||/^169\.254\./.test(s)||/^172\.(1[6-9]|2\d|3[0-1])\./.test(s)||"0.0.0.0"===s||"::1"===s))},notifyRevolutWebhookWarning(e){Quasar.Notify.create({type:"warning",message:e,icon:null,closeBtn:!0})},addStripeAllowedUser(){const e=this.formAddStripeUser||"";e.length&&!this.formData.stripe_limits.allowed_users.includes(e)&&(this.formData.stripe_limits.allowed_users=[...this.formData.stripe_limits.allowed_users,e],this.formAddStripeUser="")},removeStripeAllowedUser(e){this.formData.stripe_limits.allowed_users=this.formData.stripe_limits.allowed_users.filter(t=>t!==e)},addPaypalAllowedUser(){const e=this.formAddPaypalUser||"";e.length&&!this.formData.paypal_limits.allowed_users.includes(e)&&(this.formData.paypal_limits.allowed_users=[...this.formData.paypal_limits.allowed_users,e],this.formAddPaypalUser="")},removePaypalAllowedUser(e){this.formData.paypal_limits.allowed_users=this.formData.paypal_limits.allowed_users.filter(t=>t!==e)},addSquareAllowedUser(){const e=this.formAddSquareUser||"";e.length&&!this.formData.square_limits.allowed_users.includes(e)&&(this.formData.square_limits.allowed_users=[...this.formData.square_limits.allowed_users,e],this.formAddSquareUser="")},removeSquareAllowedUser(e){this.formData.square_limits.allowed_users=this.formData.square_limits.allowed_users.filter(t=>t!==e)},addRevolutAllowedUser(){const e=this.formAddRevolutUser||"";e.length&&!this.formData.revolut_limits.allowed_users.includes(e)&&(this.formData.revolut_limits.allowed_users=[...this.formData.revolut_limits.allowed_users,e],this.formAddRevolutUser="")},removeRevolutAllowedUser(e){this.formData.revolut_limits.allowed_users=this.formData.revolut_limits.allowed_users.filter(t=>t!==e)},checkFiatProvider(e){LNbits.api.request("PUT",`/api/v1/fiat/check/${e}`).then(e=>{const t=e.data;Quasar.Notify.create({type:t.success?"positive":"warning",message:t.message,icon:null})}).catch(LNbits.utils.notifyApiError)},createRevolutWebhook(){const e=this.calculateWebhookUrl("revolut");this.formData.revolut_payment_webhook_url=e,this.formData.revolut_api_secret_key?this.isClearnetWebhookUrl(e)?(this.creatingRevolutWebhook=!0,LNbits.api.request("POST","/api/v1/fiat/revolut/webhook",null,{url:e,endpoint:this.formData.revolut_api_endpoint,api_secret_key:this.formData.revolut_api_secret_key,api_version:this.formData.revolut_api_version}).then(e=>{const t=e.data;this.formData.revolut_payment_webhook_url=t.url,this.formData.revolut_webhook_signing_secret=t.signing_secret,Quasar.Notify.create({type:"positive",message:`Revolut webhook ${t.already_exists?"already exists":"created"}${t.id?`: ${t.id}`:""}.`,icon:null})}).catch(LNbits.utils.notifyApiError).finally(()=>{this.creatingRevolutWebhook=!1})):this.notifyRevolutWebhookWarning("Revolut webhook URL must be a clearnet URL."):this.notifyRevolutWebhookWarning("Add your Revolut API secret key before creating a webhook.")}}}),window.app.component("lnbits-admin-exchange-providers",{props:["form-data"],template:"#lnbits-admin-exchange-providers",data:()=>({exchangeData:{selectedProvider:null,showTickerConversion:!1,convertFromTicker:null,convertToTicker:null},exchangesTable:{columns:[{name:"name",align:"left",label:"Exchange Name",field:"name",sortable:!0},{name:"api_url",align:"left",label:"URL",field:"api_url",sortable:!1},{name:"path",align:"left",label:"JSON Path",field:"path",sortable:!1},{name:"exclude_to",align:"left",label:"Exclude Currencies",field:"exclude_to",sortable:!1},{name:"ticker_conversion",align:"left",label:"Ticker Conversion",field:"ticker_conversion",sortable:!1}],pagination:{sortBy:"name",rowsPerPage:100,page:1,rowsNumber:100},search:null,hideEmpty:!0}}),mounted(){this.getExchangeRateHistory()},methods:{getDefaultSetting(e){LNbits.api.getDefaultSetting(e).then(t=>{this.formData[e]=t.data.default_value})},getExchangeRateHistory(){LNbits.api.request("GET","/api/v1/rate/history",this.g.user.wallets[0].inkey).then(e=>{this.initExchangeChart(e.data)}).catch(function(e){LNbits.utils.notifyApiError(e)})},showExchangeProvidersTab(e){"exchange_providers"===e&&this.getExchangeRateHistory()},addExchangeProvider(){this.formData.lnbits_exchange_rate_providers=[{name:"",api_url:"",path:"",exclude_to:[]},...this.formData.lnbits_exchange_rate_providers]},removeExchangeProvider(e){this.formData.lnbits_exchange_rate_providers=this.formData.lnbits_exchange_rate_providers.filter(t=>t!==e)},removeExchangeTickerConversion(e,t){e.ticker_conversion=e.ticker_conversion.filter(e=>e!==t),this.formData.touch=null},addExchangeTickerConversion(){this.exchangeData.selectedProvider&&(this.exchangeData.selectedProvider.ticker_conversion.push(`${this.exchangeData.convertFromTicker}:${this.exchangeData.convertToTicker}`),this.formData.touch=null,this.exchangeData.showTickerConversion=!1)},showTickerConversionDialog(e){this.exchangeData.convertFromTicker=null,this.exchangeData.convertToTicker=null,this.exchangeData.selectedProvider=e,this.exchangeData.showTickerConversion=!0},initExchangeChart(e){this.exchangeRatesChart&&(this.exchangeRatesChart.destroy(),this.exchangeRatesChart=null);const t=e.map(e=>this.utils.formatTimestamp(e.timestamp,"HH:mm")),s=(this.formData.lnbits_price_aggregator_enabled?[{name:"Aggregator"}]:[...this.formData.lnbits_exchange_rate_providers,{name:"LNbits"}]).map(t=>({label:t.name,data:e.map(e=>e.rates[t.name]),pointStyle:!0,borderWidth:"LNbits"===t.name?4:2,tension:.4}));this.exchangeRatesChart=new Chart(this.$refs.exchangeRatesChart.getContext("2d"),{type:"line",options:{plugins:{legend:{display:!0},title:{display:!0,text:"Bitcoin Price History"}}},data:{labels:t,datasets:s}})}}}),window.app.component("lnbits-admin-security",{props:["form-data"],template:"#lnbits-admin-security",data:()=>({logs:[],formBlockedIPs:"",serverlogEnabled:!1,nostrAcceptedUrl:"",formAllowedIPs:"",formCallbackUrlRule:""}),created(){},methods:{addAllowedIPs(){const e=this.formAllowedIPs.trim(),t=this.formData.lnbits_allowed_ips;e&&e.length&&!t.includes(e)&&(this.formData.lnbits_allowed_ips=[...t,e],this.formAllowedIPs="")},removeAllowedIPs(e){const t=this.formData.lnbits_allowed_ips;this.formData.lnbits_allowed_ips=t.filter(t=>t!==e)},addBlockedIPs(){const e=this.formBlockedIPs.trim(),t=this.formData.lnbits_blocked_ips;e&&e.length&&!t.includes(e)&&(this.formData.lnbits_blocked_ips=[...t,e],this.formBlockedIPs="")},removeBlockedIPs(e){const t=this.formData.lnbits_blocked_ips;this.formData.lnbits_blocked_ips=t.filter(t=>t!==e)},addCallbackUrlRule(){const e=this.formCallbackUrlRule.trim(),t=this.formData.lnbits_callback_url_rules;e&&e.length&&!t.includes(e)&&(this.formData.lnbits_callback_url_rules=[...t,e],this.formCallbackUrlRule="")},removeCallbackUrlRule(e){const t=this.formData.lnbits_callback_url_rules;this.formData.lnbits_callback_url_rules=t.filter(t=>t!==e)},addNostrUrl(){const e=this.nostrAcceptedUrl.trim();this.removeNostrUrl(e),this.formData.nostr_absolute_request_urls.push(e),this.nostrAcceptedUrl=""},removeNostrUrl(e){this.formData.nostr_absolute_request_urls=this.formData.nostr_absolute_request_urls.filter(t=>t!==e)},async toggleServerLog(){if(this.serverlogEnabled=!this.serverlogEnabled,this.serverlogEnabled){const e="http:"!==location.protocol?"wss://":"ws://",t=await LNbits.utils.digestMessage(this.g.user.id),s=e+document.domain+":"+location.port+"/api/v1/ws/"+t;this.ws=new WebSocket(s),this.ws.addEventListener("message",async({data:e})=>{this.logs.push(e.toString());const t=this.$refs.logScroll;if(t){const e=t.getScrollTarget(),s=0;t.setScrollPosition(e.scrollHeight,s)}})}else this.ws.close()}}}),window.app.component("lnbits-admin-users",{props:["form-data"],template:"#lnbits-admin-users",data:()=>({formAddUser:"",formAddAdmin:"",formAddActivationCode:"",showReusableActivationCode:!1}),methods:{addAllowedUser(){let e=this.formAddUser,t=this.formData.lnbits_allowed_users;e&&e.length&&!t.includes(e)&&(this.formData.lnbits_allowed_users=[...t,e],this.formAddUser="")},removeAllowedUser(e){let t=this.formData.lnbits_allowed_users;this.formData.lnbits_allowed_users=t.filter(t=>t!==e)},addAdminUser(){let e=this.formAddAdmin,t=this.formData.lnbits_admin_users;e&&e.length&&!t.includes(e)&&(this.formData.lnbits_admin_users=[...t,e],this.formAddAdmin="")},removeAdminUser(e){let t=this.formData.lnbits_admin_users;this.formData.lnbits_admin_users=t.filter(t=>t!==e)},addOneTimeActivationCode(){const e=this.formAddActivationCode,t=this.formData.lnbits_register_one_time_activation_codes;e?.length&&!t.includes(e)&&(this.formData.lnbits_register_one_time_activation_codes=[...t,e],this.formAddActivationCode="")},removeOneTimeActivationCode(e){const t=this.formData.lnbits_register_one_time_activation_codes;this.formData.lnbits_register_one_time_activation_codes=t.filter(t=>t!==e)}}}),window.app.component("lnbits-admin-server",{props:["form-data"],template:"#lnbits-admin-server"}),window.app.component("lnbits-admin-extensions",{props:["form-data"],template:"#lnbits-admin-extensions",data:()=>({formAddExtensionsManifest:""}),methods:{addExtensionsManifest(){const e=this.formAddExtensionsManifest.trim(),t=this.formData.lnbits_extensions_manifests;e&&e.length&&!t.includes(e)&&(this.formData.lnbits_extensions_manifests=[...t,e],this.formAddExtensionsManifest="")},removeExtensionsManifest(e){const t=this.formData.lnbits_extensions_manifests;this.formData.lnbits_extensions_manifests=t.filter(t=>t!==e)}}}),window.app.component("lnbits-admin-wasm-runtime",{props:["form-data"],template:"#lnbits-admin-wasm-runtime",data(){return{wasmRuntimeLoading:!1,wasmHistoryLoading:!1,wasmRuntimeTimer:null,wasmStats:{},wasmCurrentInvocations:[],wasmInvocationHistory:[],wasmStatItems:[{key:"total",label:"Total",icon:"data_usage",color:"primary"},{key:"running",label:"Running",icon:"play_circle",color:"green"},{key:"completed",label:"Completed",icon:"task_alt",color:"teal"},{key:"failed",label:"Failed",icon:"error",color:"red"},{key:"stopped",label:"Stopped",icon:"stop_circle",color:"orange"},{key:"timeout",label:"Timeouts",icon:"timer_off",color:"purple"}],wasmCurrentColumns:[{name:"extension_id",label:"Extension",field:"extension_id",align:"left",sortable:!0},{name:"export_name",label:"Export",field:"export_name",align:"left",sortable:!0},{name:"trigger_type",label:"Trigger",field:"trigger_type",align:"left",sortable:!0},{name:"status",label:"Status",field:"status",align:"left",sortable:!0},{name:"user_id",label:"User",field:"user_id",align:"left",sortable:!0},{name:"started_at",label:"Started",field:"started_at",align:"left",sortable:!0},{name:"duration_ms",label:"Duration",field:e=>e.duration_ms||0,align:"right",sortable:!0},{name:"context",label:"Context",field:e=>this.wasmContextValue(e),align:"left",sortable:!0},{name:"actions",label:"",field:"actions",align:"right"}],wasmHistoryColumns:[{name:"extension_id",label:"Extension",field:"extension_id",align:"left",sortable:!0},{name:"export_name",label:"Export",field:"export_name",align:"left",sortable:!0},{name:"trigger_type",label:"Trigger",field:"trigger_type",align:"left",sortable:!0},{name:"status",label:"Status",field:"status",align:"left",sortable:!0},{name:"user_id",label:"User",field:"user_id",align:"left",sortable:!0},{name:"started_at",label:"Started",field:"started_at",align:"left",sortable:!0},{name:"duration_ms",label:"Duration",field:e=>e.duration_ms||0,align:"right",sortable:!0},{name:"calls",label:"Calls",field:e=>this.wasmCallCount(e),align:"left",sortable:!0},{name:"context",label:"Context",field:e=>this.wasmContextValue(e),align:"left",sortable:!0},{name:"error_message",label:"Error/Stop Reason",field:e=>e.error_message||e.stop_reason||"",align:"left",sortable:!0}]}},computed:{adminKey(){return this.g.user.wallets[0].adminkey},wasmExtensionId(){return this.$route.params.extId||null},wasmExtensionQuery(){return this.wasmExtensionId?`extension_id=${encodeURIComponent(this.wasmExtensionId)}`:""}},watch:{wasmExtensionId(){this.fetchWasmRuntime()}},methods:{async fetchWasmRuntime(){await Promise.all([this.fetchWasmCurrentInvocations(),this.fetchWasmInvocationHistory(),this.fetchWasmInvocationStats()])},async fetchWasmCurrentInvocations(){this.wasmRuntimeLoading=!0;try{const e=this.wasmExtensionQuery?`?${this.wasmExtensionQuery}`:"",{data:t}=await LNbits.api.request("GET",`/api/v1/extension/wasm/invocations/current${e}`,this.adminKey);this.wasmCurrentInvocations=t||[]}catch(e){LNbits.utils.notifyApiError(e)}finally{this.wasmRuntimeLoading=!1}},async fetchWasmInvocationHistory(){this.wasmHistoryLoading=!0;try{const e=["limit=50"];this.wasmExtensionQuery&&e.push(this.wasmExtensionQuery);const{data:t}=await LNbits.api.request("GET",`/api/v1/extension/wasm/invocations?${e.join("&")}`,this.adminKey);this.wasmInvocationHistory=t||[]}catch(e){LNbits.utils.notifyApiError(e)}finally{this.wasmHistoryLoading=!1}},async fetchWasmInvocationStats(){try{const e=["hours=24"];this.wasmExtensionQuery&&e.push(this.wasmExtensionQuery);const{data:t}=await LNbits.api.request("GET",`/api/v1/extension/wasm/invocations/stats?${e.join("&")}`,this.adminKey);this.wasmStats=t||{}}catch(e){LNbits.utils.notifyApiError(e)}},async stopWasmInvocation(e){try{await LNbits.api.request("POST",`/api/v1/extension/wasm/invocations/${encodeURIComponent(e)}/stop`,this.adminKey),Quasar.Notify.create({type:"positive",message:"WASM invocation stop requested."}),await this.fetchWasmRuntime()}catch(e){LNbits.utils.notifyApiError(e)}},deactivateWasmExtension(e){LNbits.utils.confirmDialog(`Deactivate extension '${e}'?`,"Deactivate Extension").onOk(async()=>{try{await LNbits.api.request("PUT",`/api/v1/extension/${encodeURIComponent(e)}/deactivate`,this.adminKey),Quasar.Notify.create({type:"positive",message:`Extension '${e}' deactivated.`}),await this.fetchWasmRuntime()}catch(e){LNbits.utils.notifyApiError(e)}})},formatWasmStat(e){const t=this.wasmStats[e];return null==t?"0":String(t)},formatWasmDate(e){return e?this.utils.formatDate(e):""},wasmStatusColor:e=>({running:"green",stopping:"orange",completed:"teal",failed:"red",stopped:"orange",timeout:"purple",abandoned:"grey"}[e]||"grey"),wasmTriggerColor:e=>({http:"primary",event:"purple"}[e]||"grey"),formatWasmDuration(e){let t=e.duration_ms;return"running"!==e.status&&"stopping"!==e.status||!e.started_at||(t=Date.now()-new Date(e.started_at).getTime()),null==t?"":t>=1e3?`${(t/1e3).toFixed(1)}s`:`${t}ms`},formatWasmCalls:e=>[`host ${e.host_call_count||0}`,`http ${e.http_call_count||0}`,`storage ${e.storage_call_count||0}`,`wallet ${e.wallet_call_count||0}`].join(" / "),wasmCallCount:e=>(e.host_call_count||0)+(e.http_call_count||0)+(e.storage_call_count||0)+(e.wallet_call_count||0),wasmContextValue:e=>[e.method,e.path,e.event_type,e.wallet_id,e.payment_hash].filter(Boolean).join(" "),formatWasmContext:e=>[e.method,e.path,e.event_type,e.wallet_id?`wallet ${e.wallet_id}`:"",e.payment_hash?`payment ${e.payment_hash.slice(0,12)}...`:""].filter(Boolean).join(" | "),formatWasmUserId(e){if(!e)return"-";const t=String(e);return t.length<=12?t:`${t.slice(0,3)}...${t.slice(-3)}`}},created(){this.fetchWasmRuntime(),this.wasmRuntimeTimer=setInterval(()=>{this.fetchWasmCurrentInvocations()},5e3)},unmounted(){this.wasmRuntimeTimer&&clearInterval(this.wasmRuntimeTimer)}}),window.app.component("lnbits-admin-wasm-limit-config",{props:["form-data"],template:"#lnbits-admin-wasm-limit-config",data:()=>({selectedWasmExtensionId:null,wasmExtensionLimitDraft:{},wasmExtensionLimitsSaving:!1,wasmRuntimeLimitExtensions:[],wasmRuntimeLimitExtensionsLoading:!1,wasmLimitInfoDialog:{show:!1,title:"",details:""},wasmRuntimeLimitGroups:[{title:"Execution",fields:[{name:"wasm_runtime_max_execution_ms",label:"Max execution time (ms)",description:"Maximum wall-clock time allowed for one WASM invocation.",details:"This is the elapsed time from starting the invocation until the export returns. When the limit is reached LNbits requests an interrupt and records the invocation as timed out if it cannot finish quickly. Use this to stop long sleeps, slow host calls, and CPU loops that run for too long."},{name:"wasm_runtime_max_fuel",label:"Max fuel",description:"Maximum Wasmtime instruction budget for one invocation.",details:"Fuel is Wasmtime instruction budgeting. It is more deterministic than wall-clock time for CPU-heavy loops because each executed instruction consumes budget. Set this low enough to stop busy loops, but high enough for legitimate extension startup and JSON processing."},{name:"wasm_runtime_max_wasm_stack_bytes",label:"Max WASM stack (bytes)",description:"Maximum stack space for WASM calls and recursion.",details:"This limits stack used by WebAssembly function calls. It protects the server from deep recursion or very large call chains in extension code. If legitimate extensions fail with stack overflow traps, raise this carefully."}]},{title:"Memory and Data Size",fields:[{name:"wasm_runtime_max_memory_bytes",label:"Max memory (bytes)",description:"Maximum WASM linear memory per invocation.",details:"This caps the linear memory visible to the WASM module. It limits memory.grow and can make instantiation fail if the module asks for too much memory up front. This does not include every byte used by the Python process or Wasmtime engine internals."},{name:"wasm_runtime_max_request_bytes",label:"Max request size (bytes)",description:"Maximum serialized input payload accepted before execution.",details:"This caps the serialized payload passed into a WASM export before execution starts. It protects against huge HTTP bodies, oversized event data, and expensive JSON parsing. Requests above this limit should be rejected before invoking the extension."},{name:"wasm_runtime_max_response_bytes",label:"Max response size (bytes)",description:"Maximum serialized response returned by a WASM export.",details:"This caps the JSON or string response returned by the WASM export. It prevents extensions from returning huge responses that consume memory, slow down API calls, or overload the browser. Responses above this limit are treated as invalid."}]},{title:"Wasmtime Objects",fields:[{name:"wasm_runtime_max_table_elements",label:"Max table elements",description:"Maximum total elements allowed in WASM tables.",details:"Tables store references used by WebAssembly, commonly function references. Limiting table elements prevents a module from allocating very large reference tables. Each table element also has host memory overhead."},{name:"wasm_runtime_max_instances",label:"Max instances",description:"Maximum WebAssembly instances allowed inside one store.",details:"This limits how many WebAssembly instances can be created inside one Wasmtime store. LNbits normally needs one instance per invocation, so a low value is expected. Raising it should only be needed if the runtime starts supporting modules that instantiate other modules."},{name:"wasm_runtime_max_tables",label:"Max tables",description:"Maximum WebAssembly tables allowed inside one store.",details:"This limits the number of WebAssembly tables in the store. It is separate from table elements: one setting limits the number of tables, the other limits their total size. Keep this small unless a component model module legitimately needs more tables."},{name:"wasm_runtime_max_memories",label:"Max memories",description:"Maximum WebAssembly linear memories allowed inside one store.",details:"This limits how many separate linear memories a module can create. Most extensions should need only one memory. Keep this small to reduce memory accounting complexity and prevent multi-memory abuse."}]},{title:"Concurrency",fields:[{name:"wasm_runtime_max_concurrent_invocations",label:"Max concurrent invocations",description:"Maximum running WASM invocations across the server.",details:"This is the global cap for running WASM invocations across all extensions and users. It protects the LNbits process from thread exhaustion, CPU pressure, and too many simultaneous stores. New invocations should be rejected or queued once this is reached."},{name:"wasm_runtime_max_concurrent_invocations_per_extension",label:"Max concurrent per extension",description:"Maximum running WASM invocations for one extension.",details:"This caps how many invocations a single extension can run at once. It prevents one malicious or buggy extension from consuming the whole global concurrency budget. Set it lower than the global limit."},{name:"wasm_runtime_max_concurrent_invocations_per_user",label:"Max concurrent per user",description:"Maximum running WASM invocations for one user.",details:"This caps concurrent invocations attributed to one user. It helps protect against a user repeatedly clicking, refreshing, or scripting extension calls. Invocations without a user can still be governed by the global and per-extension limits."}]},{title:"Host Calls",fields:[{name:"wasm_runtime_max_host_calls",label:"Max host calls",description:"Maximum total calls from WASM into LNbits host APIs.",details:"This is the total budget for calls from the WASM module into LNbits host APIs during one invocation. It should count all categories together. It limits chatty extensions and prevents tight loops that repeatedly call back into Python."},{name:"wasm_runtime_max_http_calls",label:"Max HTTP calls",description:"Maximum outbound HTTP host calls per invocation.",details:"This caps outbound HTTP requests made through the host API during one invocation. It reduces SSRF blast radius, protects network resources, and limits slow external dependencies. It should be enforced together with HTTP timeout and response-size limits."},{name:"wasm_runtime_max_storage_calls",label:"Max storage calls",description:"Maximum storage host calls per invocation.",details:"This caps extension storage operations during one invocation. It protects the database from excessive reads and writes triggered by malicious loops. Use it with storage payload-size limits if those are added later."},{name:"wasm_runtime_max_wallet_calls",label:"Max wallet calls",description:"Maximum wallet/payment host calls per invocation.",details:"This caps wallet and payment-related host calls during one invocation. These calls are security-sensitive and may touch balances, invoices, or payments. Keep this conservative and rely on explicit permissions for what the extension is allowed to do."}]},{title:"HTTP",fields:[{name:"wasm_runtime_http_timeout_ms",label:"HTTP timeout (ms)",description:"Maximum time allowed for one WASM HTTP request.",details:"This is the per-request timeout for HTTP calls made through the WASM host API. It prevents a slow remote server from holding an invocation open indefinitely. The total invocation timeout still applies across all work."},{name:"wasm_runtime_max_http_response_bytes",label:"Max HTTP response size (bytes)",description:"Maximum response body size accepted from one WASM HTTP request.",details:"This caps the response body accepted from each HTTP call made by an extension. It protects memory and parsing time when a remote server returns a very large body. Responses above the limit should fail the host call."}]}]}),computed:{adminKey(){return this.g.user.wallets[0].adminkey},isExtensionLimitRoute(){return this.$route.path.startsWith("/admin/extensions/wasm/limits/")},routeWasmExtensionId(){return this.isExtensionLimitRoute?this.$route.params.extId:null},backRoute(){return this.isExtensionLimitRoute?"/admin/extensions/wasm/limits":"/admin#extensions"},backTooltip(){return this.isExtensionLimitRoute?"Wasm Limit Config":"Extensions Settings"},pageDescription(){return this.isExtensionLimitRoute?"Customize limits for one installed WASM extension.":"These values are global defaults. Use 0 to disable a global limit."},wasmRuntimeLimitExtensionOptions(){return this.wasmRuntimeLimitExtensions.map(e=>({label:`${e.name||e.id} (${e.id})`,value:e.id}))},selectedWasmRuntimeLimitExtension(){return this.wasmRuntimeLimitExtensions.find(e=>e.id===this.selectedWasmExtensionId)||null},customWasmLimitCount(){const e=this.selectedWasmRuntimeLimitExtension;return e&&e.wasm_runtime_limits?Object.keys(e.wasm_runtime_limits).length:0}},watch:{selectedWasmExtensionId(){this.loadSelectedWasmRuntimeLimitExtension()},routeWasmExtensionId(){this.syncWasmExtensionLimitRoute()}},created(){this.fetchWasmRuntimeLimitExtensions()},methods:{async fetchWasmRuntimeLimitExtensions(){this.wasmRuntimeLimitExtensionsLoading=!0;try{const{data:e}=await LNbits.api.request("GET","/api/v1/extension/wasm/runtime-limits/extensions",this.adminKey);this.wasmRuntimeLimitExtensions=e||[],this.selectedWasmExtensionId&&!this.wasmRuntimeLimitExtensions.some(e=>e.id===this.selectedWasmExtensionId)&&(this.selectedWasmExtensionId=null),this.syncWasmExtensionLimitRoute()}catch(e){LNbits.utils.notifyApiError(e)}finally{this.wasmRuntimeLimitExtensionsLoading=!1}},syncWasmExtensionLimitRoute(){this.selectedWasmExtensionId=this.routeWasmExtensionId,this.loadSelectedWasmRuntimeLimitExtension()},openWasmExtensionLimit(e){this.$router.push(`/admin/extensions/wasm/limits/${encodeURIComponent(e)}`)},loadSelectedWasmRuntimeLimitExtension(){const e=this.selectedWasmRuntimeLimitExtension;this.wasmExtensionLimitDraft=e?{...e.wasm_runtime_limits||{}}:{}},wasmExtensionLimitHint(e){return`Inherited global value: ${this.formData[e.name]}. ${e.description}`},wasmExtensionLimitPlaceholder(e){const t=this.formData[e.name];return null==t?"":String(t)},normalizedWasmExtensionLimitDraft(){const e={};return this.wasmRuntimeLimitGroups.forEach(t=>{t.fields.forEach(t=>{const s=this.wasmExtensionLimitDraft[t.name],a="string"==typeof s?s.trim():s;if(""===a||null==a)return;const i=Number(a);if(!Number.isFinite(i)||!Number.isInteger(i)||i<0)throw new Error(`${t.label} must be a non-negative integer.`);e[t.name]=i})}),e},async clearWasmExtensionLimits(){this.wasmExtensionLimitDraft={},await this.saveWasmExtensionLimits()},async saveWasmExtensionLimits(){if(this.selectedWasmExtensionId){this.wasmExtensionLimitsSaving=!0;try{const{data:e}=await LNbits.api.request("PUT",`/api/v1/extension/wasm/runtime-limits/${encodeURIComponent(this.selectedWasmExtensionId)}`,this.adminKey,{limits:this.normalizedWasmExtensionLimitDraft()}),t=this.wasmRuntimeLimitExtensions.findIndex(t=>t.id===e.id);t>=0&&this.wasmRuntimeLimitExtensions.splice(t,1,e),this.loadSelectedWasmRuntimeLimitExtension(),Quasar.Notify.create({type:"positive",message:"WASM extension limits saved."})}catch(e){e instanceof Error&&!e.response?Quasar.Notify.create({type:"negative",message:e.message}):LNbits.utils.notifyApiError(e)}finally{this.wasmExtensionLimitsSaving=!1}}},showWasmLimitInfo(e){this.wasmLimitInfoDialog={show:!0,title:e.label,details:e.details}}}}),window.app.component("lnbits-admin-notifications",{props:["form-data"],template:"#lnbits-admin-notifications",data:()=>({nostrNotificationIdentifier:"",emailNotificationAddress:""}),methods:{sendTestEmail(){LNbits.api.request("GET","/admin/api/v1/testemail",this.g.user.wallets[0].adminkey).then(e=>{if("error"===e.data.status)throw new Error(e.data.message);this.$q.notify({message:"Test email sent!",color:"positive"})}).catch(e=>{this.$q.notify({message:e.message,color:"negative"})})},addNostrNotificationIdentifier(){const e=this.nostrNotificationIdentifier.trim(),t=this.formData.lnbits_nostr_notifications_identifiers;e&&e.length&&!t.includes(e)&&(this.formData.lnbits_nostr_notifications_identifiers=[...t,e],this.nostrNotificationIdentifier="")},removeNostrNotificationIdentifier(e){const t=this.formData.lnbits_nostr_notifications_identifiers;this.formData.lnbits_nostr_notifications_identifiers=t.filter(t=>t!==e)},addEmailNotificationAddress(){const e=this.emailNotificationAddress.trim(),t=this.formData.lnbits_email_notifications_to_emails;e&&e.length&&!t.includes(e)&&(this.formData.lnbits_email_notifications_to_emails=[...t,e],this.emailNotificationAddress="")},removeEmailNotificationAddress(e){const t=this.formData.lnbits_email_notifications_to_emails;this.formData.lnbits_email_notifications_to_emails=t.filter(t=>t!==e)}}}),window.app.component("lnbits-admin-site-customisation",{props:["form-data"],template:"#lnbits-admin-site-customisation",data:()=>({lnbits_theme_options:["classic","bitcoin","flamingo","cyber","freedom","mint","autumn","monochrome","salvador"],colors:["primary","secondary","accent","positive","negative","info","warning","red","yellow","orange"],reactionOptions:["none","confettiBothSides","confettiFireworks","confettiStars","confettiTop"],globalBorderOptions:["retro-border","hard-border","neon-border","no-border"]}),methods:{onBackgroundImageInput(e){const t=e.target.files[0];t&&this.uploadBackgroundImage(t),e.target.value=null},async uploadBackgroundImage(e){const t=new FormData;t.append("file",e);try{const{data:e}=await LNbits.api.request("POST","/api/v1/assets?public_asset=true",null,t,{headers:{"Content-Type":"multipart/form-data"}}),s=`${window.location.origin}/api/v1/assets/${e.id}/thumbnail`;this.formData.lnbits_default_bgimage=s,Quasar.Notify.create({type:"positive",message:"Background image uploaded.",icon:null})}catch(e){LNbits.utils.notifyApiError(e)}}}}),window.app.component("lnbits-admin-assets-config",{props:["form-data"],template:"#lnbits-admin-assets-config",data:()=>({newAllowedAssetMimeType:"",newNoLimitUser:""}),async created(){},methods:{addAllowedAssetMimeType(){this.newAllowedAssetMimeType&&(this.removeAllowedAssetMimeType(this.newAllowedAssetMimeType),this.formData.lnbits_assets_allowed_mime_types.push(this.newAllowedAssetMimeType),this.newAllowedAssetMimeType="",this.formData.touch=null)},removeAllowedAssetMimeType(e){const t=this.formData.lnbits_assets_allowed_mime_types.indexOf(e);-1!==t&&this.formData.lnbits_assets_allowed_mime_types.splice(t,1),this.formData.touch=null},addNewNoLimitUser(){this.newNoLimitUser&&(this.removeNoLimitUser(this.newNoLimitUser),this.formData.lnbits_assets_no_limit_users.push(this.newNoLimitUser),this.newNoLimitUser="",this.formData.touch=null)},removeNoLimitUser(e){e&&(this.formData.lnbits_assets_no_limit_users=this.formData.lnbits_assets_no_limit_users.filter(t=>t!==e),this.formData.touch=null)}}}),window.app.component("lnbits-admin-audit",{props:["form-data"],template:"#lnbits-admin-audit",data:()=>({formAddIncludePath:"",formAddExcludePath:"",formAddIncludeResponseCode:""}),methods:{addIncludePath(){if(""===this.formAddIncludePath)return;const e=this.formData.lnbits_audit_include_paths;e.includes(this.formAddIncludePath)||(this.formData.lnbits_audit_include_paths=[...e,this.formAddIncludePath]),this.formAddIncludePath=""},removeIncludePath(e){this.formData.lnbits_audit_include_paths=this.formData.lnbits_audit_include_paths.filter(t=>t!==e)},addExcludePath(){if(""===this.formAddExcludePath)return;const e=this.formData.lnbits_audit_exclude_paths;e.includes(this.formAddExcludePath)||(this.formData.lnbits_audit_exclude_paths=[...e,this.formAddExcludePath]),this.formAddExcludePath=""},removeExcludePath(e){this.formData.lnbits_audit_exclude_paths=this.formData.lnbits_audit_exclude_paths.filter(t=>t!==e)},addIncludeResponseCode(){if(""===this.formAddIncludeResponseCode)return;const e=this.formData.lnbits_audit_http_response_codes;e.includes(this.formAddIncludeResponseCode)||(this.formData.lnbits_audit_http_response_codes=[...e,this.formAddIncludeResponseCode]),this.formAddIncludeResponseCode=""},removeIncludeResponseCode(e){this.formData.lnbits_audit_http_response_codes=this.formData.lnbits_audit_http_response_codes.filter(t=>t!==e)}}}),window.app.component("lnbits-wallet-charts",{template:"#lnbits-wallet-charts",props:["paymentFilter","chartConfig"],data:()=>({debounceTimeoutValue:1337,debounceTimeout:null,chartData:[],chartDataPointCount:0,walletBalanceChart:null,walletBalanceInOut:null,walletPaymentInOut:null,colorPrimary:Quasar.colors.changeAlpha(Quasar.colors.getPaletteColor("primary"),.3),colorSecondary:Quasar.colors.changeAlpha(Quasar.colors.getPaletteColor("secondary"),.3),barOptions:{responsive:!0,maintainAspectRatio:!1,scales:{x:{stacked:!0},y:{stacked:!0}}}}),watch:{paymentFilter:{deep:!0,handler(){this.changeCharts()}},chartConfig:{deep:!0,handler(e){this.$q.localStorage.setItem("lnbits.wallets.chartConfig",e),this.changeCharts()}}},methods:{changeCharts(){this.debounceTimeout&&clearTimeout(this.debounceTimeout),this.debounceTimeout=setTimeout(async()=>{await this.fetchChartData(),this.drawCharts()},this.debounceTimeoutValue)},filterChartData(){const e=this.paymentFilter["time[ge]"]+"T00:00:00",t=this.paymentFilter["time[le]"]+"T23:59:59";let s=0,a=this.chartData.map(e=>void 0!==this.paymentFilter["amount[ge]"]?(s+=e.balance_in,{...e,balance:s,balance_out:0,count_out:0}):void 0!==this.paymentFilter["amount[le]"]?(s-=e.balance_out,{...e,balance:s,balance_in:0,count_in:0}):{...e});a=a.filter(s=>this.paymentFilter["time[ge]"]&&this.paymentFilter["time[le]"]?s.date>=e&&s.date<=t:this.paymentFilter["time[ge]"]?s.date>=e:!this.paymentFilter["time[le]"]||s.date<=t);const i=a.map(e=>new Date(e.date).toLocaleString("default",{month:"short",day:"numeric"}));return this.chartDataPointCount=a.length,{data:a,labels:i}},drawBalanceInOutChart(e,t){this.walletBalanceInOut&&this.walletBalanceInOut.destroy();const s=this.$refs.walletBalanceInOut;s&&(this.walletBalanceInOut=new Chart(s.getContext("2d"),{type:"bar",options:this.barOptions,data:{labels:t,datasets:[{label:"Balance In",borderRadius:5,data:e.map(e=>e.balance_in),backgroundColor:this.colorPrimary},{label:"Balance Out",borderRadius:5,data:e.map(e=>e.balance_out),backgroundColor:this.colorSecondary}]}}))},drawPaymentInOut(e,t){this.walletPaymentInOut&&this.walletPaymentInOut.destroy();const s=this.$refs.walletPaymentInOut;s&&(this.walletPaymentInOut=new Chart(s.getContext("2d"),{type:"bar",options:this.barOptions,data:{labels:t,datasets:[{label:"Payments In",data:e.map(e=>e.count_in),backgroundColor:this.colorPrimary},{label:"Payments Out",data:e.map(e=>-e.count_out),backgroundColor:this.colorSecondary}]}}))},drawBalanceChart(e,t){this.walletBalanceChart&&this.walletBalanceChart.destroy();const s=this.$refs.walletBalanceChart;s&&(this.walletBalanceChart=new Chart(s.getContext("2d"),{type:"line",options:{responsive:!0,maintainAspectRatio:!1},data:{labels:t,datasets:[{label:"Balance",data:e.map(e=>e.balance),pointStyle:!1,backgroundColor:this.colorPrimary,borderColor:this.colorPrimary,borderWidth:2,fill:!0,tension:.7,fill:1},{label:"Fees",data:e.map(e=>e.fee),pointStyle:!1,backgroundColor:this.colorSecondary,borderColor:this.colorSecondary,borderWidth:1,fill:!0,tension:.7,fill:1}]}}))},drawCharts(){const{data:e,labels:t}=this.filterChartData();this.chartConfig.showBalanceChart&&this.drawBalanceChart(e,t),this.chartConfig.showBalanceInOutChart&&this.drawBalanceInOutChart(e,t),this.chartConfig.showPaymentInOutChart&&this.drawPaymentInOut(e,t)},async fetchChartData(){try{const{data:e}=await LNbits.api.request("GET",`/api/v1/payments/stats/daily?wallet_id=${this.g.wallet.id}`);this.chartData=e}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}}},async created(){await this.fetchChartData(),this.drawCharts()}}),window.app.component("lnbits-wallet-api-docs",{template:"#lnbits-wallet-api-docs",methods:{copyAdminKey(){LNbits.utils.confirmDialog(this.$t("admin_key_warning")).onOk(()=>LNbits.utils.copyText(this.g.wallet.adminkey))},resetKeys(){LNbits.utils.confirmDialog("Are you sure you want to reset your API keys?").onOk(()=>{LNbits.api.resetWalletKeys(this.g.wallet).then(e=>{const{id:t,adminkey:s,inkey:a}=e;this.g.wallet={...this.g.wallet,inkey:a,adminkey:s};const i=this.g.user.wallets.findIndex(e=>e.id===t);-1!==i&&(this.g.user.wallets[i]={...this.g.user.wallets[i],inkey:a,adminkey:s}),Quasar.Notify.create({timeout:3500,type:"positive",message:"API keys reset!"})}).catch(e=>{LNbits.utils.notifyApiError(e)})})}},data:()=>({origin:window.location.origin,inkeyHidden:!0,adminkeyHidden:!0,walletIdHidden:!0})}),window.app.component("lnbits-wallet-icon",{template:"#lnbits-wallet-icon",data:()=>({icon:{show:!1,data:{},colorOptions:["primary","purple","orange","green","brown","blue","red","pink"],options:["home","star","bolt","paid","savings","store","videocam","music_note","flight","train","directions_car","school","construction","science","sports_esports","sports_tennis","theaters","water","headset_mic","videogame_asset","person","group","pets","sunny","elderly","verified","snooze","mail","forum","shopping_cart","shopping_bag","attach_money","print_connect","dark_mode","light_mode","android","network_wifi","shield","fitness_center","lunch_dining"]}}),methods:{setSelectedIcon(e){this.icon.data.icon=e},setSelectedColor(e){this.icon.data.color=e},setIcon(){this.$emit("update-wallet",this.icon.data),this.icon.show=!1}}}),window.app.component("lnbits-wallet-new",{template:"#lnbits-wallet-new",data:()=>({walletTypes:[{label:"Lightning Wallet",value:"lightning"}],wallet:{name:"",sharedWalletId:""},showNewWalletDialog:!1}),watch:{"g.newWalletType"(e){null!==e&&(this.showNewWalletDialog=!0)},showNewWalletDialog(e){!0!==e&&this.reset()}},computed:{isLightning(){return"lightning"===this.g.newWalletType},isLightningShared(){return"lightning-shared"===this.g.newWalletType},inviteWalletOptions(){return(this.g.user?.extra?.wallet_invite_requests||[]).map(e=>({label:`${e.to_wallet_name} (from ${e.from_user_name})`,value:e.to_wallet_id}))}},methods:{reset(){this.showNewWalletDialog=!1,this.g.newWalletType=null,this.wallet={name:"",sharedWalletId:""}},async submitRejectWalletInvitation(){try{const e=this.g.user.extra.wallet_invite_requests||[],t=e.find(e=>e.to_wallet_id===this.wallet.sharedWalletId);if(!t)return void Quasar.Notify.create({message:"Cannot find invitation for the selected wallet.",type:"warning"});await LNbits.api.request("DELETE",`/api/v1/wallet/share/invite/${t.request_id}`,this.g.wallet.adminkey),Quasar.Notify.create({message:"Invitation rejected.",type:"positive"}),this.g.user.extra.wallet_invite_requests=e.filter(e=>e.request_id!==t.request_id)}catch(e){LNbits.utils.notifyApiError(e)}},submitAddWallet(){const e=this.wallet;"lightning"!==this.g.newWalletType||e.name?"lightning-shared"!==this.g.newWalletType||e.sharedWalletId?LNbits.api.createWallet(e.name,this.g.newWalletType,{shared_wallet_id:e.sharedWalletId}).then(e=>{this.$q.notify({message:"Wallet created successfully",color:"positive"}),this.reset(),this.g.user.wallets.push(LNbits.map.wallet(e.data)),this.g.lastWalletId=e.data.id,this.$router.push(`/wallet/${e.data.id}`)}).catch(LNbits.utils.notifyApiError):this.$q.notify({message:"Missing a shared wallet ID",color:"warning"}):this.$q.notify({message:"Please enter a name for the wallet",color:"warning"})}},created(){this.g.user?.extra?.wallet_invite_requests?.length&&this.walletTypes.push({label:`Lightning Wallet (Share Invite: ${this.g.user.extra.wallet_invite_requests.length})`,value:"lightning-shared"})}}),window.app.component("lnbits-wallet-share",{template:"#lnbits-wallet-share",computed:{walletApprovedShares(){return this.g.wallet.extra.shared_with.filter(e=>"approved"===e.status)},walletPendingRequests(){return this.g.wallet.extra.shared_with.filter(e=>"request_access"===e.status)},walletPendingInvites(){return this.g.wallet.extra.shared_with.filter(e=>"invite_sent"===e.status)}},data:()=>({permissionOptions:[{label:"View",value:"view-payments"},{label:"Receive",value:"receive-payments"},{label:"Send",value:"send-payments"}],walletShareInvite:{username:"",permissions:[]}}),methods:{async updateSharePermissions(e){try{const{data:t}=await LNbits.api.request("PUT","/api/v1/wallet/share",this.g.wallet.adminkey,e);Object.assign(e,t),Quasar.Notify.create({message:"Wallet permission updated.",type:"positive"})}catch(e){LNbits.utils.notifyApiError(e)}},async inviteUserToWallet(){try{const{data:e}=await LNbits.api.request("PUT","/api/v1/wallet/share/invite",this.g.wallet.adminkey,{...this.walletShareInvite,status:"invite_sent",wallet_id:this.g.wallet.id});this.g.wallet.extra.shared_with.push(e),this.walletShareInvite={username:"",permissions:[]},Quasar.Notify.create({message:"User invited to wallet.",type:"positive"})}catch(e){LNbits.utils.notifyApiError(e)}},deleteSharePermission(e){LNbits.utils.confirmDialog("Are you sure you want to remove this share permission?").onOk(async()=>{try{await LNbits.api.request("DELETE",`/api/v1/wallet/share/${e.request_id}`,this.g.wallet.adminkey),this.g.wallet.extra.shared_with=this.g.wallet.extra.shared_with.filter(t=>t.wallet_id!==e.wallet_id),Quasar.Notify.create({message:"Wallet permission deleted.",type:"positive"})}catch(e){LNbits.utils.notifyApiError(e)}})}}}),window.app.component("lnbits-wallet-paylinks",{template:"#lnbits-wallet-paylinks",data:()=>({storedPaylinks:[]}),watch:{"g.wallet"(e){this.storedPaylinks=e.storedPaylinks??[]}},created(){this.storedPaylinks=this.g.wallet.storedPaylinks},methods:{updatePaylinks(){LNbits.api.request("PUT",`/api/v1/wallet/stored_paylinks/${this.g.wallet.id}`,this.g.wallet.adminkey,{links:this.storedPaylinks}).then(()=>{this.$q.notify({message:"Paylinks updated.",type:"positive",timeout:3500})}).catch(e=>{LNbits.utils.notifyApiError(e)})},sendToPaylink(e){this.$emit("send-lnurl",e)},editPaylink(){this.$nextTick(()=>{this.updatePaylinks()})},deletePaylink(e){const t=[];this.storedPaylinks.forEach(s=>{s.lnurl!==e&&t.push(s)}),this.storedPaylinks=t,this.updatePaylinks()}}}),window.app.component("lnbits-wallet-extra",{template:"#lnbits-wallet-extra",props:["chartConfig"],computed:{exportUrl(){return`${window.location.origin}/wallet?usr=${this.g.user.id}&wal=${this.g.wallet.id}`}},methods:{handleSendLnurl(e){this.$emit("send-lnurl",e)},updateWallet(e){this.$emit("update-wallet",e)},handleFiatTracking(){this.g.fiatTracking=!this.g.fiatTracking,this.g.fiatTracking?(this.updateWallet({currency:this.g.wallet.currency}),this.updateFiatBalance()):(this.g.isFiatPriority=!1,this.g.wallet.currency="",this.updateWallet({currency:""}))},deleteWallet(){LNbits.utils.confirmDialog("Are you sure you want to delete this wallet?").onOk(()=>{LNbits.api.deleteWallet(this.g.wallet).then(()=>{this.g.user.wallets=this.g.user.wallets.filter(e=>e.id!==this.g.wallet.id),this.g.lastActiveWallet=this.g.user.wallets[0].id,this.$router.push(`/wallet/${this.g.lastActiveWallet}`),Quasar.Notify.create({timeout:3e3,message:"Wallet deleted!",spinner:!0})}).catch(e=>{LNbits.utils.notifyApiError(e)})})},updateFiatBalance(){this.$q.localStorage.getItem("lnbits.exchangeRate."+this.g.wallet.currency)&&(this.g.exchangeRate=this.$q.localStorage.getItem("lnbits.exchangeRate."+this.g.wallet.currency),this.g.fiatBalance=this.g.exchangeRate/1e8*this.g.wallet.sat),LNbits.api.request("GET","/api/v1/rate/"+this.g.wallet.currency,null).then(e=>{this.g.fiatBalance=e.data.price/1e8*this.g.wallet.sat,this.g.exchangeRate=e.data.price.toFixed(2),this.g.fiatTracking=!0,this.$q.localStorage.set("lnbits.exchangeRate."+this.g.wallet.currency,this.g.exchangeRate),this.g.exchangeRate<=0&&(this.g.fiatTracking=!1,this.g.isFiatPriority=!1)}).catch(e=>console.error(e))}},created(){""!==this.g.wallet.currency&&this.g.isSatsDenomination?(this.g.fiatTracking=!0,this.updateFiatBalance()):this.g.fiatTracking=!1}}),window.app.component("lnbits-home-logos",{template:"#lnbits-home-logos",data:()=>({logos:[{href:"https://github.com/ElementsProject/lightning",lightSrc:"/static/images/clnl.png",darkSrc:"/static/images/cln.png"},{href:"https://github.com/lightningnetwork/lnd",lightSrc:"/static/images/lnd.png",darkSrc:"/static/images/lnd.png"},{href:"https://opennode.com",lightSrc:"/static/images/opennodel.png",darkSrc:"/static/images/opennode.png"},{href:"https://lnpay.co/",lightSrc:"/static/images/lnpayl.png",darkSrc:"/static/images/lnpay.png"},{href:"https://github.com/rootzoll/raspiblitz",lightSrc:"/static/images/blitzl.png",darkSrc:"/static/images/blitz.png"},{href:"https://start9.com/",lightSrc:"/static/images/start9l.png",darkSrc:"/static/images/start9.png"},{href:"https://getumbrel.com/",lightSrc:"/static/images/umbrell.png",darkSrc:"/static/images/umbrel.png"},{href:"https://mynodebtc.com",lightSrc:"/static/images/mynodel.png",darkSrc:"/static/images/mynode.png"},{href:"https://github.com/shesek/spark-wallet",lightSrc:"/static/images/sparkl.png",darkSrc:"/static/images/spark.png"},{href:"https://voltage.cloud",lightSrc:"/static/images/voltagel.png",darkSrc:"/static/images/voltage.png"},{href:"https://breez.technology/sdk/",lightSrc:"/static/images/breezl.png",darkSrc:"/static/images/breez.png"},{href:"https://blockstream.com/lightning/greenlight/",lightSrc:"/static/images/greenlightl.png",darkSrc:"/static/images/greenlight.png"},{href:"https://getalby.com",lightSrc:"/static/images/albyl.png",darkSrc:"/static/images/alby.png"},{href:"https://zbd.gg",lightSrc:"/static/images/zbdl.png",darkSrc:"/static/images/zbd.png"},{href:"https://phoenix.acinq.co/server",lightSrc:"/static/images/phoenixdl.png",darkSrc:"/static/images/phoenixd.png"},{href:"https://boltz.exchange/",lightSrc:"/static/images/boltzl.svg",darkSrc:"/static/images/boltz.svg"},{href:"https://www.blink.sv/",lightSrc:"/static/images/blink_logol.png",darkSrc:"/static/images/blink_logo.png"}]}),computed:{showLogos(){return this.g.isSatsDenomination&&"LNbits"==this.g.settings.siteTitle&&1==this.g.settings.showHomePageElements}}}),window.app.component("lnbits-error",{template:"#lnbits-error",props:["dynamic","code","message"],computed:{isExtension(){return 403==this.code&&(!!this.message.startsWith("Extension ")||void 0)}},methods:{goBack(){window.history.back()},goHome(){window.location="/"},goToWallet(){this.dynamic?this.$router.push("/wallet"):window.location="/wallet"},goToExtension(){const e=`/extensions#${this.message.match(/'([^']+)'/)[1]}`;this.dynamic?this.$router.push(e):window.location=e},async logOut(){try{await LNbits.api.logout(),window.location="/"}catch(e){LNbits.utils.notifyApiError(e)}}},async created(){if(!this.dynamic&&401==this.code)return console.warn(`Unauthorized: ${this.errorMessage}`),void this.logOut()}}),window.app.component("lnbits-qrcode",{template:"#lnbits-qrcode",components:{QrcodeVue:QrcodeVue.default},props:{value:{type:String,required:!0},nfc:{type:Boolean,default:!1},print:{type:Boolean,default:!1},showButtons:{type:Boolean,default:!0},href:{type:String,default:""},margin:{type:Number,default:3},maxWidth:{type:Number,default:450},logo:{type:String,default:window.g.settings.qrLogo||null}},data:()=>({nfcTagWriting:!1,nfcSupported:"undefined"!=typeof NDEFReader}),methods:{printQrCode(){const e=this.$refs.qrCode.$el.outerHTML,t=window.open("","_blank");t.document.write(`\n \n \n Print QR Code\n \n \n ${e}\n \n `),t.document.close(),t.focus(),t.print(),t.close()},clickQrCode(e){if(""===this.href)return this.utils.copyText(this.value),e.preventDefault(),e.stopPropagation(),!1;this.href&&this.href.startsWith("http")&&(window.open(this.href,"_blank"),e.preventDefault())},async writeNfcTag(){try{if(!this.nfcSupported)throw{toString:function(){return"NFC not supported on this device or browser."}};const e=new NDEFReader;this.nfcTagWriting=!0,this.$q.notify({message:"Tap your NFC tag to write the LNURL-withdraw link to it."}),await e.write({records:[{recordType:"url",data:this.value,lang:"en"}]}),this.nfcTagWriting=!1,this.$q.notify({type:"positive",message:"NFC tag written successfully."})}catch(e){this.nfcTagWriting=!1,this.$q.notify({type:"negative",message:e?e.toString():"An unexpected error has occurred."})}},downloadSVG(){const e=this.$refs.qrCode.$el;if(!e)return void console.error("SVG element not found");let t=(new XMLSerializer).serializeToString(e);t.match(/^]+xmlns="http:\/\/www\.w3\.org\/2000\/svg"/)||(t=t.replace(/^({tab:"bech32",lnurl:""}),methods:{setLnurl(){if("bech32"==this.tab){const e=(new TextEncoder).encode(this.url),t=NostrTools.nip19.encodeBytes("lnurl",e);this.lnurl=this.href&&""!==this.href.trim()?`${this.href}?lightning=${t.toUpperCase()}`:`lightning:${t.toUpperCase()}`}else"lud17"==this.tab&&(this.url.startsWith("http://")?this.lnurl=this.url.replace("http://",this.prefix+"://"):this.lnurl=this.url.replace("https://",this.prefix+"://"));this.$emit("update:lnurl",this.lnurl)}},watch:{url(){this.setLnurl()},tab(){this.setLnurl()}},created(){this.setLnurl()}}),window.app.component("lnbits-disclaimer",{template:"#lnbits-disclaimer",computed:{showDisclaimer:()=>!g.disclaimerShown&&g.isUserAuthorized}}),window.app.component("lnbits-footer",{template:"#lnbits-footer",computed:{title(){return`${this.g.settings.siteTitle}, ${this.g.settings.siteTagline}`},version(){return this.$t("lnbits_version")+": "+this.g.settings.version}}}),window.app.component("lnbits-header",{template:"#lnbits-header",computed:{showAdmin(){return this.g.user&&(this.g.user.super_user||this.g.user.admin)},displayName(){return this.g.user?.extra?.display_name||this.g.user.username||this.g.user?.extra?.first_name||"Anon"},displayRole(){return this.g.user?.super_user?"Super User":this.g.user?.admin?"Admin":"User"}},methods:{async stopImpersonation(){try{await LNbits.api.stopImpersonation(),LNbits.utils.restoreLocalStorage("impersonation"),window.location="/users"}catch(e){console.warn(e)}},async handleLanguageChanged(e){try{await LNbits.api.updateUiCustomization({locale:e.locale}),this.$q.notify({type:"positive",message:"Language Updated",caption:e.locale})}catch(e){LNbits.utils.notifyApiError(e)}}}}),window.app.component("lnbits-header-wallets",{template:"#lnbits-header-wallets"}),window.app.component("lnbits-drawer",{template:"#lnbits-drawer"}),window.app.component("lnbits-theme",{watch:{"g.walletFlip"(e){this.$q.localStorage.setItem("lnbits.walletFlip",e),!0===e&&this.$q.screen.lt.md&&(this.g.visibleDrawer=!1)},"g.disclaimerShown"(e){this.$q.localStorage.setItem("lnbits.disclaimerShown",e)},"g.locale"(e){this.$q.localStorage.setItem("lnbits.lang",e),window.i18n.global.locale=e},"g.isFiatPriority"(e){this.$q.localStorage.setItem("lnbits.isFiatPriority",e)},"g.reactionChoice"(e){this.$q.localStorage.set("lnbits.reactions",e)},"g.themeChoice"(e){document.body.setAttribute("data-theme",e),this.$q.localStorage.set("lnbits.theme",e)},"g.darkChoice"(e){this.$q.dark.set(e),this.$q.localStorage.set("lnbits.darkMode",e),Chart.defaults.color=this.$q.dark.isActive?"#fff":"#000"},"g.borderChoice"(e){document.body.classList.forEach(e=>{e.endsWith("-border")&&document.body.classList.remove(e)}),this.$q.localStorage.setItem("lnbits.border",e),document.body.classList.add(e)},"g.gradientChoice"(e){this.$q.localStorage.set("lnbits.gradientBg",e),!0===e?document.body.classList.add("gradient-bg"):document.body.classList.remove("gradient-bg")},"g.cardRoundedChoice"(e){this.$q.localStorage.set("lnbits.cardRounded",e),!0===e?document.body.classList.add("rounded-ui"):document.body.classList.remove("rounded-ui")},"g.cardGradientChoice"(e){this.$q.localStorage.set("lnbits.cardGradient",e),!0===e?document.body.classList.add("card-gradient"):document.body.classList.remove("card-gradient")},"g.cardShadowChoice"(e){this.$q.localStorage.set("lnbits.cardShadow",e),!0===e?document.body.classList.add("card-shadow"):document.body.classList.remove("card-shadow")},"g.burgerMenuChoice"(e){this.$q.localStorage.set("lnbits.burgerMenu",e),!0===e?document.body.classList.remove("no-burger-background"):document.body.classList.add("no-burger-background")},"g.mobileSimple"(e){this.$q.localStorage.set("lnbits.mobileSimple",e),!0===e?document.body.classList.add("mobile-simple"):document.body.classList.remove("mobile-simple")},"g.bgimageChoice"(e){this.$q.localStorage.set("lnbits.backgroundImage",e),""===e?document.body.classList.remove("bg-image"):(document.body.classList.add("bg-image"),document.body.style.setProperty("--background",`url(${e})`))}},methods:{async checkUrlParams(){const e=new URLSearchParams(window.location.search);if(0===e.length)return;if(e.has("theme")){const t=e.get("theme").trim().toLowerCase();this.g.themeChoice=t,e.delete("theme")}if(e.has("border")){const t=e.get("border").trim().toLowerCase();this.g.borderChoice=t,e.delete("border")}if(e.has("gradient")){const t=e.get("gradient").toLowerCase();this.g.gradientChoice="1"===t||"true"===t,e.delete("gradient")}if(e.has("dark")){const t=e.get("dark").trim().toLowerCase();this.g.darkChoice="1"===t||"true"===t,e.delete("dark")}if(e.has("usr")){try{await LNbits.api.loginUsr(e.get("usr")),window.location.href="/wallet"}catch(e){LNbits.utils.notifyApiError(e)}e.delete("usr")}const t=e.size?`?${e.toString()}`:"",s=window.location.pathname+t;window.history.replaceState(null,null,s)}},created(){this.$q.dark.set(this.g.darkChoice),document.body.setAttribute("data-theme",this.g.themeChoice),Chart.defaults.color=this.$q.dark.isActive?"#fff":"#000",document.body.classList.add(this.g.borderChoice),!0===this.g.gradientChoice&&document.body.classList.add("gradient-bg"),!0===this.g.cardRoundedChoice&&document.body.classList.add("rounded-ui"),!0===this.g.cardGradientChoice&&document.body.classList.add("card-gradient"),!0===this.g.cardShadowChoice&&document.body.classList.add("card-shadow"),!0!==this.g.burgerMenuChoice&&document.body.classList.add("no-burger-background"),""!==this.g.bgimageChoice&&(document.body.classList.add("bg-image"),document.body.style.setProperty("--background",`url(${this.g.bgimageChoice})`)),!0===this.g.mobileSimple&&document.body.classList.add("mobile-simple"),Object.entries(this.g.user?.uiCustomization||{}).forEach(([e,t])=>{e in this.g&&(this.g[e]=t)}),this.checkUrlParams()}}),window.app.component("lnbits-qrcode-scanner",{template:"#lnbits-qrcode-scanner",props:["callback"],data:()=>({showScanner:!1}),watch:{callback(e){return null===e?this.reset():"function"!=typeof e?(Quasar.Notify.create({message:"QR code scanner callback is not a function.",type:"negative"}),this.reset()):!1===this.g.hasCamera?(Quasar.Notify.create({message:"No camera found on this device.",type:"negative"}),this.reset()):void(this.showScanner=!0)}},methods:{reset(){this.showScanner=!1,this.g.scanner=null},detect(e){const t=e[0].rawValue;console.log("Detected QR code value:",t),this.callback(t),this.$emit("detect",t),this.reset()},async onInitQR(e){try{await e}catch(e){const t={NotAllowedError:"ERROR: you need to grant camera access permission",NotFoundError:"ERROR: no camera on this device",NotSupportedError:"ERROR: secure context required (HTTPS, localhost)",NotReadableError:"ERROR: is the camera already in use?",OverconstrainedError:"ERROR: installed cameras are not suitable",StreamApiNotSupportedError:"ERROR: Stream API is not supported in this browser",InsecureContextError:"ERROR: Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP."},s=Object.keys(t).filter(t=>e.name===t),a=s?t[s]:`ERROR: Camera error (${e.name})`;Quasar.Notify.create({message:a,type:"negative"}),this.g.hasCamera=!1,this.reset()}}}}),window.app.component("lnbits-manage-extension-list",{template:"#lnbits-manage-extension-list",data:()=>({extensions:[],userExtensions:[],searchTerm:""}),watch:{"g.user.extensions"(){this.loadExtensions()},searchTerm(){this.filterUserExtensionsByTerm()}},methods:{async loadExtensions(){try{res=await LNbits.api.request("GET","/api/v1/extension"),this.extensions=res.data.sort((e,t)=>e.name.localeCompare(t.name)),this.filterUserExtensionsByTerm()}catch(e){LNbits.utils.notifyApiError(e)}},filterUserExtensionsByTerm(){const e=this.g.user.extensions;this.userExtensions=this.extensions.filter(t=>e.includes(t.code)).filter(e=>""===this.searchTerm||`${e.code} ${e.name} ${e.short_description} ${e.url}`.toLocaleLowerCase().includes(this.searchTerm.toLocaleLowerCase()))},extensionUrl:e=>e.is_wasm?`/ext/${e.code}`:`/${e.code}/`,extensionActive(e){const t=e.is_wasm?`/ext/${e.code}`:`/${e.code}`;return this.$route.path.startsWith(t)}},async created(){await this.loadExtensions()}}),function(){function e(e,t){return e?e(t):t}function t(t,s){const a=function(e){return`extension_permission_${e.id.replace(/[^A-Za-z0-9]/g,"_")}`}(t),i=e(s,a);return i===a?t.id:i}function s(t){return{level:"low",color:"grey-6",label:e(t,"extension_permission_risk_low"),warning:""}}function a(t,s){return{level:"medium",color:"warning",label:e(t,"extension_permission_risk_medium"),warning:s?e(t,s):""}}function i(t,s){return{level:"high",color:"negative",label:e(t,"extension_permission_risk_high"),warning:e(t,s)}}function n(e,t){const s=(e||[]).find(e=>e.id===t);return s?.name||t}function o(e,t){const s=e.policies;return Array.isArray(s)?s.map(e=>{const s="string"==typeof e?e:e?.id;if(!s)return null;const a="string"==typeof e?["read"]:Array.isArray(e.access)&&e.access.length?e.access:["read"];return{id:s,name:n(t,s),access:a}}).filter(Boolean):[]}function r(e,t,n){const r=e.map(e=>function(e,t,n){if(["wallet.pay_invoice","wallet.pay_invoice_background"].includes(e.id))return i(n,"wallet.pay_invoice_background"===e.id?"extension_permission_warning_wallet_pay_invoice_background":"extension_permission_warning_wallet_pay_invoice");if("extension.api.request"===e.id)return o(e,t).some(e=>e.access.includes("write"))?i(n,"extension_permission_warning_extension_api_request_write"):a(n);return"http.request"===e.id?a(n):"wallet.payments.watch"===e.id?a(n,"extension_permission_warning_wallet_payments_watch"):["websocket.publish","websocket.subscribe"].includes(e.id)||["wallet.list","wallet.balance.read","wallet.create_invoice_public","ext.storage.append_public","ext.storage.read_public"].includes(e.id)?a(n):s(n)}(e,t,n)),l=r.find(e=>"high"===e.level);return l||(r.find(e=>"medium"===e.level)||s(n))}function l(e){const t=["wallet.pay_invoice","wallet.pay_invoice_background","wallet.payments.watch","wallet.list","wallet.balance.read","extension.api.request","http.request","ui.camera.scan_qr","websocket","websocket.publish","websocket.subscribe","ext.storage.read","ext.storage.write","ext.storage.read_public","ext.storage.append_public","wallet.create_invoice_public","wallet.create_invoice","utils.basic"],s=t.indexOf(e);return-1===s?t.length:s}function c(s,a,i){const n=s[0],l=2===s.length&&s.some(e=>"ext.storage.read"===e.id)&&s.some(e=>"ext.storage.write"===e.id),c=s.every(e=>["websocket.publish","websocket.subscribe"].includes(e.id))&&s.some(e=>"websocket.publish"===e.id)&&s.some(e=>"websocket.subscribe"===e.id),d=s.map(e=>function(e){return"string"==typeof e.description?e.description:""}(e)).filter(Boolean),h={id:l?"ext.storage.read_write":c?"websocket":n.id,label:l?e(i,"extension_permission_ext_storage_read_write"):c?e(i,"extension_permission_websocket"):t(n,i),risk:r(s,a,i),badges:[],descriptions:d,fieldGroups:[],appendPolicies:[],websocketPublishPolicies:[],invoicePolicies:[],extensionAccess:[],httpHosts:[]};"ext.storage.read_public"===n.id&&(h.fieldGroups=function(e){const t=e.policies;return Array.isArray(t)?t.map(e=>{const t="string"==typeof e?e:e?.table_name||"",s="string"!=typeof e&&Array.isArray(e?.public_fields)?e.public_fields.filter(e=>"string"==typeof e&&e):[],a="string"==typeof e||"string"!=typeof e?.source_id_field?"":e.source_id_field;return t?{table:t,fields:s,sourceIdField:a}:null}).filter(Boolean):[]}(n),h.badges=h.fieldGroups.map(e=>({key:e.table,label:e.table}))),"extension.api.request"===n.id&&(h.extensionAccess=o(n,a),h.badges=h.extensionAccess.map(e=>({key:e.id,label:e.name}))),"http.request"===n.id&&(h.httpHosts=function(e){const t=e.policies;return Array.isArray(t)?t.map(e=>"string"==typeof e?e:e?.host||"").filter(e=>"string"==typeof e&&e):[]}(n)),"wallet.create_invoice_public"===n.id&&(h.invoicePolicies=function(e){const t=e.policies;return Array.isArray(t)?t.map(e=>{if(!e||"object"!=typeof e)return null;const t=e.table,s=e.wallet_field;return"string"==typeof t&&t&&"string"==typeof s&&s?{table:t,walletField:s}:null}).filter(Boolean):[]}(n)),"ext.storage.append_public"===n.id&&(h.appendPolicies=function(e){const t=e.policies;return Array.isArray(t)?t.map(e=>{if(!e||"object"!=typeof e)return null;const t=e.table,s=e.source_table,a=e.source_id_field,i=Array.isArray(e.allowed_fields)?e.allowed_fields.filter(e=>"string"==typeof e&&e):[],n=Number.isInteger(e.max_rows_per_source)?e.max_rows_per_source:1e4;return"string"==typeof t&&t&&"string"==typeof s&&s&&"string"==typeof a&&a?{table:t,sourceTable:s,sourceIdField:a,allowedFields:i,maxRowsPerSource:n,rawPolicy:e}:null}).filter(Boolean):[]}(n),h.badges=h.appendPolicies.map(e=>({key:e.table+":"+e.sourceTable+":"+e.sourceIdField,label:e.table})));const m=s.find(e=>"websocket.publish"===e.id);return m&&(h.websocketPublishPolicies=function(e){const t=e.policies;return Array.isArray(t)?t.map(e=>e&&"object"==typeof e?{maxMessagesPerSecond:Number.isInteger(e.max_messages_per_second)?e.max_messages_per_second:0,rawPolicy:e}:null).filter(Boolean):[]}(m)),h}function d({permissions:e,extensions:t,translate:s}){const a=e||[],i=new Map(a.map(e=>[e.id,e])),n=i.has("ext.storage.read")&&i.has("ext.storage.write"),o=i.has("websocket.publish")&&i.has("websocket.subscribe");let r=!1,d=!1;return a.map((e,t)=>n&&["ext.storage.read","ext.storage.write"].includes(e.id)?r?null:(r=!0,{index:t,orderId:"ext.storage.read",permissions:[i.get("ext.storage.read"),i.get("ext.storage.write")]}):o&&["websocket.publish","websocket.subscribe"].includes(e.id)?d?null:(d=!0,{index:t,orderId:"websocket",permissions:[i.get("websocket.publish"),i.get("websocket.subscribe")]}):{index:t,orderId:e.id,permissions:[e]}).filter(Boolean).sort((e,t)=>{const s=l(e.orderId),a=l(t.orderId);return s===a?e.index-t.index:s-a}).map(e=>c(e.permissions,t||[],s))}window.LNbitsExtensionPermissions={displayItems:d,hasHighRisk:({permissions:e,extensions:t,translate:s})=>d({permissions:e,extensions:t,translate:s}).some(e=>"high"===e.risk.level)},window.app.component("lnbits-extension-permissions",{template:"#lnbits-extension-permissions",props:{permissions:{type:Array,default:()=>[]},extensions:{type:Array,default:()=>[]},editableAppendPublicLimits:{type:Boolean,default:!1},maxRowsPerSourceLimit:{type:Number,default:1e6},editableWebsocketPublishLimits:{type:Boolean,default:!1},maxMessagesPerSecondLimit:{type:Number,default:100}},computed:{displayItems(){return window.LNbitsExtensionPermissions.displayItems({permissions:this.permissions,extensions:this.extensions,translate:e=>this.$t(e)})}},methods:{publicInvoicePolicySentence:e=>`Invoices will be created using ${e.walletField} from ${e.table}.`,publicAppendPolicySentence:e=>`${e.table} rows can be appended for ${e.sourceTable} using ${e.sourceIdField}. Limit: ${e.maxRowsPerSource} rows per source.`,websocketPublishPolicySentence:e=>`Limit: ${e.maxMessagesPerSecond} messages per second.`,permissionAccessLabel(e){const t=`extension_permission_access_${e}`,s=this.$t(t);return s===t?e:s}}})}(),window.app.component("lnbits-manage-wallet-list",{template:"#lnbits-manage-wallet-list",data:()=>({activeWalletId:null}),computed:{maxWallets(){return this.g.user?.extra?.visible_wallet_count||10}},watch:{$route(e){e.path.startsWith("/wallet/")?this.activeWalletId=e.params.id:this.activeWalletId=null},"g.user.wallets":{handler(){this.paymentEvents()},deep:!0,immediate:!0}},created(){this.g.user&&0===this.g.walletEventListeners.length&&this.paymentEvents()},methods:{openNewWalletDialog(){this.g.user.walletInvitesCount?this.g.newWalletType="lightning-shared":this.g.newWalletType="lightning"},onWebsocketMessage(e){const t=JSON.parse(e.data);t.payment?(this.g.user.wallets.forEach(e=>{e.id===t.payment.wallet_id&&(e.sat=t.wallet_balance)}),this.g.wallet.id===t.payment.wallet_id&&(this.g.wallet.sat=t.wallet_balance,this.g.updatePayments=!this.g.updatePayments,this.g.updatePaymentsHash=!this.g.updatePaymentsHash),t.payment.amount>0&&eventReaction(1e3*t.wallet_balance)):console.error("ws message no payment",t)},paymentEvents(){if(!this.g.user)return;let e;this.g.user.wallets.slice(0,this.maxWallets).forEach(t=>{if(!this.g.walletEventListeners.includes(t.id)){this.g.walletEventListeners.push(t.id);const s=new WebSocket(`${websocketUrl}/${t.inkey}`);s.onmessage=this.onWebsocketMessage,s.onopen=()=>console.log("ws connected for wallet",t.id),s.onclose=()=>{console.log("ws closed, reconnecting...",t.id),this.g.walletEventListeners=this.g.walletEventListeners.filter(e=>e!==t.id),clearTimeout(e),e=setTimeout(this.paymentEvents,5e3)},s.onerror=()=>{console.warn("ws error, reconnecting...",t.id),this.g.walletEventListeners=this.g.walletEventListeners.filter(e=>e!==t.id),clearTimeout(e),e=setTimeout(this.paymentEvents,5e3)}}})}}}),window.app.component("lnbits-language-dropdown",{template:"#lnbits-language-dropdown",computed:{currentLanguage(){return this.langs.find(e=>e.value===window.i18n.global.locale)||{value:"en",label:"English",display:"🇬🇧 EN"}}},methods:{activeLanguage:e=>window.i18n.global.locale===e,changeLanguage(e){this.g.locale=e,window.i18n.global.locale=e,this.$q.localStorage.set("lnbits.lang",e),this.$emit("language-changed",e)}},data:()=>({langs:[{value:"en",label:"English",display:"🇬🇧 EN"},{value:"de",label:"Deutsch",display:"🇩🇪 DE"},{value:"es",label:"Español",display:"🇪🇸 ES"},{value:"jp",label:"日本語",display:"🇯🇵 JP"},{value:"cn",label:"中文",display:"🇨🇳 CN"},{value:"fr",label:"Français",display:"🇫🇷 FR"},{value:"it",label:"Italiano",display:"🇮🇹 IT"},{value:"pi",label:"Pirate",display:"🏴‍☠️ PI"},{value:"nl",label:"Nederlands",display:"🇳🇱 NL"},{value:"we",label:"Cymraeg",display:"🏴󠁧󠁢󠁷󠁬󠁳󠁿 CY"},{value:"pl",label:"Polski",display:"🇵🇱 PL"},{value:"pt",label:"Português",display:"🇵🇹 PT"},{value:"br",label:"Português do Brasil",display:"🇧🇷 BR"},{value:"cs",label:"Česky",display:"🇨🇿 CS"},{value:"sk",label:"Slovensky",display:"🇸🇰 SK"},{value:"kr",label:"한국어",display:"🇰🇷 KR"},{value:"fi",label:"Suomi",display:"🇫🇮 FI"}]})}),window.app.component("lnbits-payment-list",{template:"#lnbits-payment-list",props:["wallet","paymentFilter"],data(){return{payments:[],paymentsTable:{columns:[{name:"time",align:"left",label:this.$t("memo")+"/"+this.$t("date"),field:"date",sortable:!0},{name:"amount",align:"right",label:this.$t("amount"),field:"sat",sortable:!0}],pagination:{rowsPerPage:10,page:1,sortBy:"time",descending:!0,rowsNumber:10},sortFields:[{name:"amount",label:"Amount"},{name:"fee",label:"Fee"},{name:"memo",label:"Memo"},{name:"time",label:"Creation Date"},{name:"updated_at",label:"Last Updated"}],search:"",loading:!1},searchDate:{from:null,to:null},searchStatus:{success:!0,pending:!0,failed:!1,incoming:!0,outgoing:!0},exportTagName:"",exportPaymentTagList:[],paymentsCSV:{columns:[{name:"status",align:"right",label:this.$t("status"),field:"status"},{name:"memo",align:"left",label:this.$t("memo"),field:"memo"},{name:"time",align:"left",label:this.$t("date"),field:"date",sortable:!0},{name:"amount",align:"right",label:this.$t("amount"),field:"sat",sortable:!0},{name:"fee",align:"right",label:this.$t("fee"),field:"fee"},{name:"tag",align:"right",label:this.$t("tag"),field:"tag"},{name:"payment_hash",align:"right",label:this.$t("payment_hash"),field:"payment_hash"},{name:"payment_proof",align:"right",label:this.$t("payment_proof"),field:"payment_proof"},{name:"webhook",align:"right",label:this.$t("webhook"),field:"webhook"},{name:"fiat_currency",align:"right",label:"Fiat Currency",field:e=>e.extra.wallet_fiat_currency},{name:"fiat_amount",align:"right",label:"Fiat Amount",field:e=>e.extra.wallet_fiat_amount}],preimage:null,loading:!1},hodlInvoice:{show:!1,payment:null,preimage:null},selectedPayment:null,filterLabels:[]}},computed:{filteredPayments(){const e=this.paymentsTable.search;return e&&""!==e?LNbits.utils.search(this.payments,e):this.payments},paymentsOmitter(){return this.$q.screen.lt.md&&this.g.mobileSimple?this.payments.length>0?[this.payments[0]]:[]:this.payments},pendingPaymentsExist(){return-1!==this.payments.findIndex(e=>e.pending)}},methods:{mapPayment(e){const t={checking_id:e.checking_id,status:e.status,amount:e.amount,fee:e.fee,memo:e.memo,time:e.time,bolt11:e.bolt11,preimage:e.preimage,payment_hash:e.payment_hash,expiry:e.expiry,extra:e.extra??{},wallet_id:e.wallet_id,webhook:e.webhook,webhook_status:e.webhook_status,fiat_amount:e.fiat_amount,fiat_currency:e.fiat_currency,labels:e.labels};t.date=this.utils.formatDate(e.created_at),t.dateFrom=this.utils.formatDateFrom(e.created_at),t.expirydate=this.utils.formatDate(e.expiry),t.expirydateFrom=this.utils.formatDateFrom(e.expiry),t.msat=t.amount,t.sat=t.msat/1e3,t.tag=t.extra?.tag,t.fsat=this.utils.formatSat(t.sat),t.isIn=t.amount>0,t.isOut=t.amount<0,t.isPending="pending"===t.status,t.isPaid="success"===t.status,t.isFailed="failed"===t.status,t._q=[t.memo,t.sat].join(" ").toLowerCase();try{t.details=JSON.parse(e.extra?.details||"{}")}catch{t.details={extraDetails:e.extra?.details}}return t},searchByDate(){"string"==typeof this.searchDate&&(this.searchDate={from:this.searchDate,to:this.searchDate}),this.searchDate.from&&(this.paymentFilter["time[ge]"]=this.searchDate.from+"T00:00:00"),this.searchDate.to&&(this.paymentFilter["time[le]"]=this.searchDate.to+"T23:59:59"),this.fetchPayments()},searchByLabels(e){e&&0!==e.length?(this.filterLabels=e,this.paymentsTable.filter["labels[every]"]=e,this.fetchPayments()):this.clearLabelSeach()},clearDateSeach(){this.searchDate={from:null,to:null},delete this.paymentFilter["time[ge]"],delete this.paymentFilter["time[le]"],this.fetchPayments()},clearLabelSeach(){this.filterLabels=[],delete this.paymentsTable.filter["labels[every]"],this.fetchPayments()},fetchPayments(e){this.paymentsTable.loading=!0;const t=LNbits.utils.prepareFilterQuery(this.paymentsTable,e,this.paymentFilter);return LNbits.api.getPayments(this.wallet,t).then(e=>{this.paymentsTable.pagination.rowsNumber=e.data.total,this.payments=e.data.data.map(this.mapPayment),this.paymentsTable.loading=!1,this.recheckPendingPayments()}).catch(e=>{this.paymentsTable.loading=!1,g.user.admin?this.fetchPaymentsAsAdmin(this.wallet.id,t):LNbits.utils.notifyApiError(e)})},sortByColumn(e){this.paymentsTable.pagination.sortBy===e?this.paymentsTable.pagination.descending=!this.paymentsTable.pagination.descending:(this.paymentsTable.pagination.sortBy=e,this.paymentsTable.pagination.descending=!1),this.fetchPayments()},fetchPaymentsAsAdmin(e,t){return t=(t||"")+"&wallet_id="+e,LNbits.api.request("GET","/api/v1/payments/all/paginated?"+t).then(e=>{this.paymentsTable.loading=!1,this.paymentsTable.pagination.rowsNumber=e.data.total,this.payments=e.data.data.map(this.mapPayment)}).catch(e=>{this.paymentsTable.loading=!1,LNbits.utils.notifyApiError(e)})},checkPayment(e){LNbits.api.getPayment(this.wallet,e).then(e=>{this.update=!this.update,"success"==e.data.status&&Quasar.Notify.create({type:"positive",message:this.$t("payment_successful")}),"pending"==e.data.status&&Quasar.Notify.create({type:"info",message:this.$t("payment_pending")})}).catch(LNbits.utils.notifyApiError)},recheckPendingPayments(){const e=this.payments.filter(e=>"pending"===e.status);if(0===e.length)return;const t=["recheck_pending=true","checking_id[in]="+e.map(e=>e.checking_id).join(",")].join("&");LNbits.api.getPayments(this.wallet,t).then(e=>{let t=0;e.data.data.forEach(e=>{if("pending"!==e.status){const s=this.payments.findIndex(t=>t.checking_id===e.checking_id);-1!==s&&(this.payments.splice(s,1,this.mapPayment(e)),t+=1)}}),t>0&&Quasar.Notify.create({type:"positive",message:this.$t("payment_successful")})}).catch(e=>{console.warn(e)})},showHoldInvoiceDialog(e){this.hodlInvoice.show=!0,this.hodlInvoice.preimage="",this.hodlInvoice.payment=e},cancelHoldInvoice(e){LNbits.api.cancelInvoice(this.wallet,e).then(()=>{this.update=!this.update,Quasar.Notify.create({type:"positive",message:this.$t("invoice_cancelled")})}).catch(LNbits.utils.notifyApiError)},settleHoldInvoice(e){LNbits.api.settleInvoice(this.wallet,e).then(()=>{this.update=!this.update,Quasar.Notify.create({type:"positive",message:this.$t("invoice_settled")})}).catch(LNbits.utils.notifyApiError)},paymentTableRowKey:e=>e.payment_hash+e.amount,async exportCSV(e=!1){const t=this.paymentsTable.pagination,s=1e3;let a=[];this.paymentsCSV.loading=!0;try{for(let e=0;e<100;e++){const i={sortby:t.sortBy??"time",direction:t.descending?"desc":"asc",limit:s,offset:e*s},n=new URLSearchParams(i),o=await LNbits.api.getPayments(this.wallet,n),r=o.data.data||[];if(a=a.concat(r.map(this.mapPayment)),r.length=o.data.total)break}let i=this.paymentsCSV.columns;if(e){this.exportPaymentTagList.length&&(a=a.filter(e=>this.exportPaymentTagList.includes(e.tag)));const e=Object.keys(a.reduce((e,t)=>({...e,...t.details}),{})).map(e=>({name:e,align:"right",label:e.charAt(0).toUpperCase()+e.slice(1).replace(/([A-Z])/g," $1"),field:t=>t.details[e],format:e=>"object"==typeof e?JSON.stringify(e):e}));i=this.paymentsCSV.columns.concat(e)}LNbits.utils.exportCSV(i,a,this.wallet.name+"-payments")}catch(e){LNbits.utils.notifyApiError(e)}finally{this.paymentsCSV.loading=!1}},addFilterTag(){if(!this.exportTagName)return;const e=this.exportTagName.trim();this.exportPaymentTagList=this.exportPaymentTagList.filter(t=>t!==e),this.exportPaymentTagList.push(e),this.exportTagName=""},removeExportTag(e){this.exportPaymentTagList=this.exportPaymentTagList.filter(t=>t!==e)},formatCurrency(e,t){try{return LNbits.utils.formatCurrency(e,t)}catch(t){return console.error(t),`${e} ???`}},handleFilterChanged(){const{success:e,pending:t,failed:s,incoming:a,outgoing:i}=this.searchStatus;let n=this.paymentFilter||{};delete n["status[ne]"],delete n["status[eq]"],e&&t&&s||(e&&t?n["status[ne]"]="failed":e&&s?n["status[ne]"]="pending":s&&t?n["status[ne]"]="success":!e||t||s?!t||e||s?!s||e||t||(n["status[eq]"]="failed"):n["status[eq]"]="pending":n["status[eq]"]="success"),delete n["amount[ge]"],delete n["amount[le]"],a&&i||!a&&!i||(a&&!i?n["amount[ge]"]=0:i&&!a&&(n["amount[le]"]=0)),this.paymentFilter=n},async savePaymentLabels(e){if(this.selectedPayment)try{await LNbits.api.request("PUT",`/api/v1/payments/${this.selectedPayment.payment_hash}/labels`,this.wallet.adminkey,{labels:e});const t=this.payments.find(e=>e.checking_id===this.selectedPayment.checking_id);t&&(t.labels=[...e]),Quasar.Notify.create({type:"positive",message:this.$t("payment_labels_updated")})}catch(e){LNbits.utils.notifyApiError(e)}else Quasar.Notify.create({type:"warning",message:"No payment selected"})},isLightColor(e){try{return Quasar.colors.luminosity(e)>.5}catch(e){return console.warning(e),!1}}},watch:{"paymentsTable.search":{handler(){const e={};this.paymentsTable.search&&(e.search=this.paymentsTable.search),this.fetchPayments()}},"g.updatePayments"(){this.fetchPayments()}},created(){this.fetchPayments()}}),window.app.component("lnbits-label-selector",{template:"#lnbits-label-selector",props:["labels"],data:()=>({labelFilter:"",localLabels:[]}),methods:{toggleLabel(e){if(this.localLabels.includes(e.name)){const t=this.localLabels.indexOf(e.name);-1!==t&&this.localLabels.splice(t,1)}else this.localLabels.push(e.name)},saveLabels(){this.$emit("update:labels",this.localLabels)},clearLabels(){this.localLabels=[],this.saveLabels()}},created(){this.localLabels=[...this.labels]}}),window.app.component("lnbits-extension-settings-form",{name:"lnbits-extension-settings-form",template:"#lnbits-extension-settings-form",props:["options","adminkey","endpoint"],methods:{async updateSettings(){if(!this.settings)return Quasar.Notify.create({message:"No settings to update",type:"negative"});try{const{data:e}=await LNbits.api.request("PUT",this.endpoint,this.adminkey,this.settings);this.settings=e}catch(e){LNbits.utils.notifyApiError(e)}},async getSettings(){try{const{data:e}=await LNbits.api.request("GET",this.endpoint,this.adminkey);this.settings=e}catch(e){LNbits.utils.notifyApiError(e)}},async resetSettings(){LNbits.utils.confirmDialog("Are you sure you want to reset the settings?").onOk(async()=>{try{await LNbits.api.request("DELETE",this.endpoint,this.adminkey),await this.getSettings()}catch(e){LNbits.utils.notifyApiError(e)}})}},async created(){await this.getSettings()},data:()=>({settings:void 0})}),window.app.component("lnbits-extension-settings-btn-dialog",{template:"#lnbits-extension-settings-btn-dialog",name:"lnbits-extension-settings-btn-dialog",props:["options","adminkey","endpoint"],data:()=>({show:!1})}),window.app.component("lnbits-data-fields",{name:"lnbits-data-fields",template:"#lnbits-data-fields",props:["fields","hide-advanced"],data:()=>({fieldTypes:[{label:"Text",value:"str"},{label:"Integer",value:"int"},{label:"Float",value:"float"},{label:"Boolean",value:"bool"},{label:"Date Time",value:"datetime"},{label:"JSON",value:"json"},{label:"Wallet Select",value:"wallet"},{label:"Currency Select",value:"currency"}],fieldsTable:{columns:[{name:"name",align:"left",label:"Field Name",field:"name",sortable:!0},{name:"type",align:"left",label:"Type",field:"type",sortable:!1},{name:"label",align:"left",label:"UI Label",field:"label",sortable:!0},{name:"hint",align:"left",label:"UI Hint",field:"hint",sortable:!1},{name:"optional",align:"left",label:"Optional",field:"optional",sortable:!1}],pagination:{sortBy:"name",rowsPerPage:100,page:1,rowsNumber:100},search:null,hideEmpty:!0}}),methods:{addField:function(){this.fields.push({name:"field_name_"+(this.fields.length+1),type:"text",label:"",hint:"",optional:!0,sortable:!0,searchable:!0,editable:!0,fields:[]})},removeField:function(e){const t=this.fields.indexOf(e);t>-1&&this.fields.splice(t,1)}},async created(){this.hideAdvanced||this.fieldsTable.columns.push({name:"editable",align:"left",label:"UI Editable",field:"editable",sortable:!1},{name:"sortable",align:"left",label:"Sortable",field:"sortable",sortable:!1},{name:"searchable",align:"left",label:"Searchable",field:"searchable",sortable:!1})}}),window.app.component(QrcodeVue),window.app.component("lnbits-extension-rating",{template:"#lnbits-extension-rating",name:"lnbits-extension-rating",props:{rating:{type:Number,default:0},count:{type:Number,default:null},clickable:{type:Boolean,default:!1}},computed:{displayRating(){return Math.round(2*(this.rating||0))/2},hasData(){return null!==this.count&&void 0!==this.count}},methods:{handleClick(){this.clickable&&this.$emit("click")}}}),window.app.component("lnbits-manage",{template:"#lnbits-manage",methods:{isActive:e=>window.location.pathname===e},data:()=>({extensions:[]})}),window.app.component("lnbits-payment-details",{template:"#lnbits-payment-details",props:["payment"],computed:{hasPreimage(){return this.payment.preimage&&"0000000000000000000000000000000000000000000000000000000000000000"!==this.payment.preimage},hasExpiry(){return!!this.payment.expiry},hasSuccessAction(){return this.hasPreimage&&this.payment.extra&&this.payment.extra.success_action},webhookStatusColor(){return this.payment.webhook_status>=300||this.payment.webhook_status<0?"red-10":this.payment.webhook_status?"green-10":"cyan-7"},webhookStatusText(){return this.payment.webhook_status?this.payment.webhook_status:"not sent yet"},hasTag(){return this.payment.extra&&!!this.payment.extra.tag},extras(){if(!this.payment.extra)return[];let e=_.omit(this.payment.extra,["tag","success_action"]);return Object.keys(e).map(t=>({key:t,value:e[t]}))}}}),window.app.component("lnbits-lnurlpay-success-action",{template:"#lnbits-lnurlpay-success-action",props:["payment","success_action"],data(){return{decryptedValue:this.success_action.ciphertext}},mounted(){if("aes"!==this.success_action.tag)return null;this.utils.decryptLnurlPayAES(this.success_action,this.payment.preimage).then(e=>{this.decryptedValue=e})}}),window.app.component("lnbits-notifications-btn",{template:"#lnbits-notifications-btn",props:["pubkey"],data:()=>({isSupported:!1,isSubscribed:!1,isPermissionGranted:!1,isPermissionDenied:!1}),methods:{urlB64ToUint8Array(e){const t=(e+"=".repeat((4-e.length%4)%4)).replace(/\-/g,"+").replace(/_/g,"/"),s=atob(t),a=new Uint8Array(s.length);for(let e=0;et!==e),this.$q.localStorage.set("lnbits.webpush.subscribedUsers",JSON.stringify(t))},isUserSubscribed(e){return(JSON.parse(this.$q.localStorage.getItem("lnbits.webpush.subscribedUsers"))||[]).includes(e)},subscribe(){this.isSupported&&!this.isPermissionDenied&&(Notification.requestPermission().then(e=>{this.isPermissionGranted="granted"===e,this.isPermissionDenied="denied"===e}).catch(console.log),navigator.serviceWorker.ready.then(e=>{navigator.serviceWorker.getRegistration().then(e=>{e.pushManager.getSubscription().then(t=>{if(null===t||!this.isUserSubscribed(this.g.user.id)){const t={applicationServerKey:this.urlB64ToUint8Array(this.pubkey),userVisibleOnly:!0};e.pushManager.subscribe(t).then(e=>{LNbits.api.request("POST","/api/v1/webpush",null,{subscription:JSON.stringify(e)}).then(e=>{this.saveUserSubscribed(e.data.user),this.isSubscribed=!0}).catch(LNbits.utils.notifyApiError)})}}).catch(console.log)})}))},unsubscribe(){navigator.serviceWorker.ready.then(e=>{e.pushManager.getSubscription().then(e=>{e&&LNbits.api.request("DELETE","/api/v1/webpush?endpoint="+btoa(e.endpoint),null).then(()=>{this.removeUserSubscribed(this.g.user.id),this.isSubscribed=!1}).catch(LNbits.utils.notifyApiError)})}).catch(console.log)},checkSupported(){let e="https:"===window.location.protocol,t="serviceWorker"in navigator,s="Notification"in window,a="PushManager"in window;return this.isSupported=e&&t&&s&&a,this.isSupported||console.log("Notifications disabled because requirements are not met:",{HTTPS:e,"Service Worker API":t,"Notification API":s,"Push API":a}),this.isSupported},async updateSubscriptionStatus(){await navigator.serviceWorker.ready.then(e=>{e.pushManager.getSubscription().then(e=>{this.isSubscribed=!!e&&this.isUserSubscribed(this.g.user.id)})}).catch(console.log)}},created(){this.isPermissionDenied="denied"===Notification.permission,this.checkSupported()&&this.updateSubscriptionStatus()}}),window.app.component("lnbits-dynamic-fields",{template:"#lnbits-dynamic-fields",props:["options","modelValue"],data:()=>({formData:null,rules:[e=>!!e||"Field is required"]}),methods:{applyRules(e){return e?this.rules:[]},buildData(e,t={}){return e.reduce((e,s)=>(s.options?.length?e[s.name]=this.buildData(s.options,t[s.name]):e[s.name]=t[s.name]??s.default,e),{})},handleValueChanged(){this.$emit("update:model-value",this.formData)}},created(){this.formData=this.buildData(this.options,this.modelValue)}}),window.app.component("lnbits-dynamic-chips",{template:"#lnbits-dynamic-chips",props:["modelValue"],data:()=>({chip:"",chips:[]}),methods:{addChip(){this.chip&&(this.chips.push(this.chip),this.chip="",this.$emit("update:model-value",this.chips.join(",")))},removeChip(e){this.chips.splice(e,1),this.$emit("update:model-value",this.chips.join(","))}},created(){"string"==typeof this.modelValue?this.chips=this.modelValue.split(","):this.chips=[...this.modelValue]}}),window.app.component("lnbits-update-balance",{template:"#lnbits-update-balance",props:["wallet_id","small_btn"],computed:{admin(){return!0===this.g.user?.super_user}},data:()=>({credit:0}),methods:{updateBalance(e){LNbits.api.updateBalance(e.value,this.wallet_id).then(t=>{if(!0!==t.data.success)throw new Error(t.data);credit=parseInt(e.value),Quasar.Notify.create({type:"positive",message:this.$t("credit_ok",{amount:credit}),icon:null}),this.credit=0,e.value=0,e.set()}).catch(LNbits.utils.notifyApiError)}}}),window.app.component("user-id-only",{template:"#user-id-only",props:{allowed_new_users:Boolean,authAction:String,authMethod:String,usr:String,wallet:String},data(){return{user:this.usr,walletName:this.wallet}},methods:{showLogin(e){this.$emit("show-login",e)},showRegister(e){this.$emit("show-register",e)},loginUsr(){this.$emit("update:usr",this.user),this.$emit("login-usr")},createWallet(){this.$emit("update:wallet",this.walletName),this.$emit("create-wallet")}},computed:{showInstantLogin(){return"username-password"!==this.authMethod||"register"!==this.authAction}},created(){}}),window.app.component("username-password",{template:"#username-password",props:{allowed_new_users:Boolean,authMethods:Array,authAction:String,username:String,password_1:String,password_2:String,invitationCode:String,resetKey:String},data(){return{oauth:["nostr-auth-nip98","google-auth","github-auth","keycloak-auth","oidc-auth"],username:this.userName,password:this.password_1,passwordRepeat:this.password_2,reset_key:this.resetKey,confirmationMethod:"code",confirmationEmail:"",confirmationCode:this.invitationCode||"",showConfirmationCode:!1,showPwd:!1,showPwdRepeat:!1}},methods:{login(){this.$emit("update:userName",this.username),this.$emit("update:password_1",this.password),this.$emit("login")},register(){this.$emit("update:userName",this.username),this.$emit("update:password_1",this.password),this.$emit("update:password_2",this.passwordRepeat),this.$emit("update:invitationCode",this.confirmationCode),this.$emit("register")},reset(){this.$emit("update:resetKey",this.reset_key),this.$emit("update:password_1",this.password),this.$emit("update:password_2",this.passwordRepeat),this.$emit("reset")},validateUsername:e=>new RegExp("^(?=[a-zA-Z0-9._]{2,20}$)(?!.*[_.]{2})[^_.].*[^_.]$").test(e),async signInWithNostr(){try{const e=await this.createNostrToken();if(!e)return;resp=await LNbits.api.loginByProvider("nostr",{Authorization:e},{}),window.location.href="/wallet"}catch(e){console.warn(e);const t=e?.response?.data?.detail||`${e}`;Quasar.Notify.create({type:"negative",message:"Failed to sign in with Nostr.",caption:t})}},async createNostrToken(){try{if(!window.nostr?.signEvent)return void Quasar.Notify.create({type:"negative",message:"No Nostr signing app detected.",caption:'Is "window.nostr" present?'});const e=`${window.location}nostr`,t="POST",s=await NostrTools.nip98.getToken(e,t,e=>async function(e){try{const{data:t}=await LNbits.api.getServerHealth();return e.created_at=t.server_time,await window.nostr.signEvent(e)}catch(e){console.error(e),Quasar.Notify.create({type:"negative",message:"Failed to sign nostr event.",caption:`${e}`})}}(e),!0);if(!await NostrTools.nip98.validateToken(s,e,t))throw new Error("Invalid signed token!");return s}catch(e){console.warn(e),Quasar.Notify.create({type:"negative",message:"Failed create Nostr event.",caption:`${e}`})}}},computed:{showOauth(){return this.oauth.some(e=>this.authMethods.includes(e))},disableRegister(){const e=!!this.username,t=!!this.password&&this.password.length>=8,s=this.password===this.passwordRepeat,a=0===this.confirmationMethodsCount||"code"!==this.confirmationMethod||this.confirmationCode.length>0;return!(e&&t&&s&&a)},confirmationMethodsCount(){return[this.g.settings.userActivationByEmail,this.g.settings.userActivationByPayment,this.g.settings.userActivationByInvitationCode].filter(Boolean).length}},created(){}}),window.app.component("separator-text",{template:"#separator-text",props:{text:String,uppercase:{type:Boolean,default:!1},color:{type:String,default:"grey"}}}),window.app.component("lnbits-node-ranks",{props:["ranks"],data:()=>({stats:[{label:"Capacity",key:"capacity"},{label:"Channels",key:"channelcount"},{label:"Age",key:"age"},{label:"Growth",key:"growth"},{label:"Availability",key:"availability"}]}),template:"\n \n
\n
1ml Node Rank
\n
\n
\n
{{ stat.label }}
\n
\n {{ (ranks && ranks[stat.key]) ?? '-' }}\n
\n
\n
\n
\n \n "}),window.app.component("lnbits-channel-stats",{props:["stats"],data:()=>({states:[{label:"Active",value:"active",color:"green"},{label:"Pending",value:"pending",color:"orange"},{label:"Inactive",value:"inactive",color:"grey"},{label:"Closed",value:"closed",color:"red"}]}),template:"\n \n
\n
Channels
\n
\n
\n
\n {{ state.label }}\n
\n
\n {{ (stats?.counts && stats.counts[state.value]) ?? \"-\" }}\n
\n
\n
\n
\n
\n "}),window.app.component("lnbits-stat",{props:["title","amount","msat","btc"],computed:{value(){return this.amount??(this.btc?LNbits.utils.formatSat(this.btc):LNbits.utils.formatMsat(this.msat))}},template:"\n \n \n
\n {{ title }}\n
\n
\n {{ value }}\n sats\n BTC\n
\n
\n
\n "}),window.app.component("lnbits-node-qrcode",{props:["info"],template:'\n \n \n
\n
\n \n
\n No addresses available\n
\n
\n
\n
\n \n Public Key Click to copy \n \n \n
\n '}),window.app.component("lnbits-channel-balance",{props:["balance","color"],methods:{formatMsat:e=>LNbits.utils.formatMsat(e)},template:'\n
\n
\n \n Local: {{ formatMsat(balance.local_msat) }}\n sats\n \n \n Remote: {{ formatMsat(balance.remote_msat) }}\n sats\n \n
\n\n \n
\n \n {{ balance.alias }}\n \n
\n \n
\n '}),window.app.component("lnbits-node-info",{props:["info"],data:()=>({showDialog:!1}),methods:{shortenNodeId:e=>e?e.substring(0,5)+"..."+e.substring(e.length-5):"..."},template:"\n
\n
{{ this.info.alias }}
\n
\n
{{ this.info.backend_name }}
\n \n #{{ this.info.color }}\n \n
{{ shortenNodeId(this.info.id) }}
\n \n \n
\n \n \n \n
\n "}),window.app.component("lnbits-stat",{props:["title","amount","msat","btc"],computed:{value(){return this.amount??(this.btc?LNbits.utils.formatSat(this.btc):LNbits.utils.formatMsat(this.msat))}},template:"\n \n \n
\n {{ title }}\n
\n
\n {{ value }}\n sats\n BTC\n
\n
\n
\n "}),window.WasmExtensionComponent={template:'\n
\n \n \n \n \n {{ error }}\n \n \n \n \n \n
Camera access
\n
\n \n {{ cameraPrompt.extensionName }} wants to access the camera to scan a QR code.\n \n \n \n \n \n \n
\n
\n \n \n \n
Background payments
\n
\n \n
\n {{ backgroundPaymentPrompt.extensionName }} wants permission to make\n background payments from\n {{ backgroundPaymentPrompt.walletName }}.\n
\n \n This permission can move funds later without an active click.\n \n \n \n
\n \n \n \n \n
\n
\n \n \n \n
Watch wallet payments
\n
\n \n
\n {{ walletPaymentWatchPrompt.extensionName }} wants permission to receive\n payment notifications for\n {{ walletPaymentWatchPrompt.walletName }}.\n
\n \n This permission exposes payment metadata for this wallet to the extension.\n \n
\n \n \n \n \n
\n
\n \n \n \n
Open link
\n
\n \n
\n {{ newTabPrompt.extensionName }} wants to open this link in a new\n tab.\n
\n \n {{ newTabPrompt.url }}\n
\n \n This link is not on the same domain as this LNbits page.\n \n \n \n \n \n \n \n \n \n \n ',data:()=>({allowedPaymentHashes:new Set,bridge:{apiRoutes:[],extensionId:"",permissions:[],public:!1,query:{},routeParams:{}},bridgePort:null,cameraPrompt:{extensionName:"",reject:null,resolve:null,show:!1},backgroundPaymentDestinationOptions:[{label:"Only transfers to my wallets",value:"own_wallets_only"},{label:"Allow external payments",value:"external_allowed"}],backgroundPaymentPrompt:{extensionName:"",form:{destinationPolicy:"own_wallets_only",maxAmount:0},reject:null,resolve:null,show:!1,walletId:"",walletName:""},walletPaymentWatchPrompt:{extensionName:"",reject:null,resolve:null,show:!1,walletId:"",walletName:""},newTabPrompt:{extensionName:"",external:!1,reject:null,resolve:null,show:!1,url:""},error:"",extensionName:"",frameUrl:"",handleWindowMessage:null,loading:!1,loadId:0,paymentSubscriptions:new Map,websocketSubscriptions:new Map}),created(){this.handleWindowMessage=e=>this.onWindowMessage(e),window.addEventListener("message",this.handleWindowMessage)},unmounted(){window.removeEventListener("message",this.handleWindowMessage),this.rejectCameraPrompt("Camera scan cancelled."),this.rejectBackgroundPaymentPrompt("Background payment permission cancelled."),this.rejectWalletPaymentWatchPrompt("Wallet payment watch permission cancelled."),this.rejectNewTabPrompt("Open link cancelled."),this.closeBridgePort()},watch:{"$route.fullPath":{immediate:!0,handler(){this.loadFrameConfig()}}},methods:{emptyBridge:()=>({apiRoutes:[],extensionId:"",permissions:[],public:!1,query:{},routeParams:{}}),plainBridgeContext(){return{extensionId:String(this.bridge.extensionId||""),public:Boolean(this.bridge.public),routeParams:this.plainValue(this.bridge.routeParams||{}),query:this.plainValue(this.bridge.query||{})}},hasBridgePermission(e){return(this.bridge.permissions||[]).includes(e)},cameraPromptStorageKey(){return`lnbits.ext.permissions.${this.bridge.extensionId}.ui.camera.scan_qr`},emptyBackgroundPaymentPrompt:()=>({extensionName:"",form:{destinationPolicy:"own_wallets_only",maxAmount:0},reject:null,resolve:null,show:!1,walletId:"",walletName:""}),emptyCameraPrompt:()=>({extensionName:"",reject:null,resolve:null,show:!1}),emptyWalletPaymentWatchPrompt:()=>({extensionName:"",reject:null,resolve:null,show:!1,walletId:"",walletName:""}),emptyNewTabPrompt:()=>({extensionName:"",external:!1,reject:null,resolve:null,show:!1,url:""}),plainValue(e){try{return JSON.parse(JSON.stringify(e))}catch(e){return{}}},async loadFrameConfig(){const e=String(this.$route.params.extId||""),t=++this.loadId;this.loading=!0,this.error="",this.frameUrl="",this.bridge=this.emptyBridge(),this.allowedPaymentHashes.clear(),this.rejectCameraPrompt("Camera scan cancelled."),this.rejectBackgroundPaymentPrompt("Background payment permission cancelled."),this.rejectWalletPaymentWatchPrompt("Wallet payment watch permission cancelled."),this.rejectNewTabPrompt("Open link cancelled."),this.closeBridgePort();try{const s=await fetch(`/api/v1/ext/${encodeURIComponent(e)}/_ui/frame`,{method:"POST",headers:{"content-type":"application/json"},credentials:"same-origin",body:JSON.stringify({path:this.$route.path,query:this.$route.query||{}})}),a=await s.text();let i={};if(a)try{i=JSON.parse(a)}catch(e){i={detail:a}}if(!s.ok)throw new Error(i?.detail||"Failed to load extension page.");if(t!==this.loadId)return;this.bridge=i.bridge||this.emptyBridge(),this.extensionName=i.extension?.name||e,this.frameUrl=i.frameUrl}catch(e){if(t!==this.loadId)return;console.error("[lnbits wasm extension] Failed to load frame.",e),this.error=e instanceof Error?e.message:String(e)}finally{t===this.loadId&&(this.loading=!1)}},extensionFrameWindow(){return this.$refs.frame?.contentWindow},sendResponse(e,t,s){e({type:"lnbits-extension:response",id:t,...s})},allowedApiRoute(e,t){let s;try{s=new URL(t,window.location.origin)}catch(e){return!1}return s.origin===window.location.origin&&(e=String(e||"GET").toUpperCase(),(this.bridge.apiRoutes||[]).some(t=>t.method===e&&new RegExp(t.pattern).test(s.pathname)))},extensionRoute(e){let t;try{t=new URL(String(e||""),window.location.origin)}catch(e){throw new Error("Invalid extension route.")}if(t.origin!==window.location.origin)throw new Error("Extension route must stay on this server.");const s=`/ext/${encodeURIComponent(this.bridge.extensionId)}`;if(t.pathname!==s&&!t.pathname.startsWith(`${s}/`))throw new Error("Extension route must stay inside this extension.");return`${t.pathname}${t.search}${t.hash}`},replaceExtensionRoute(e){return this.$router.replace(this.extensionRoute(e.path))},newTabUrl(e){const t=String(e||"").trim();if(!t)throw new Error("Open link needs a URL.");let s;try{s=new URL(t,window.location.href)}catch(e){throw new Error("Invalid open link URL.")}if(!["http:","https:"].includes(s.protocol))throw new Error("Only HTTP and HTTPS links can be opened.");if(s.username||s.password)throw new Error("Links with embedded credentials cannot be opened.");return{external:s.origin!==window.location.origin,url:s.href}},openNewTab(e){return this.promptNewTabOpen(this.newTabUrl(e.url||e.href))},promptNewTabOpen(e){if(this.newTabPrompt.show)throw new Error("Open link prompt is already open.");return new Promise((t,s)=>{this.newTabPrompt={extensionName:this.extensionName||this.bridge.extensionId||"This extension",external:e.external,reject:s,resolve:t,show:!0,url:e.url}})},resolveNewTabPrompt(e){const t=this.newTabPrompt;if(t.show)if(this.newTabPrompt=this.emptyNewTabPrompt(),e)try{window.open(t.url,"_blank","noopener,noreferrer"),t.resolve?.({external:t.external,opened:!0,url:t.url})}catch(e){t.reject?.(e)}else t.reject?.(new Error("Open link denied by user."))},rejectNewTabPrompt(e){const t=this.newTabPrompt.reject;this.newTabPrompt=this.emptyNewTabPrompt(),t?.(new Error(e))},async copyNewTabLink(){const e=this.newTabPrompt;if(e.show&&e.url)try{await navigator.clipboard.writeText(e.url),this.notify({level:"positive",message:"Link copied."})}catch(e){this.notify({level:"negative",message:"Could not copy link."})}},bridgeSessionStorageKey(e){const t=String(e||"").trim();if(!t||t.length>128||!/^[A-Za-z0-9._:-]+$/.test(t))throw new Error("Invalid extension session key.");return`lnbits.ext.session.${this.bridge.extensionId}.${t}`},getBridgeSessionValue(e){const t=this.bridgeSessionStorageKey(e.key);return{value:window.sessionStorage.getItem(t)||""}},setBridgeSessionValue(e){const t=this.bridgeSessionStorageKey(e.key),s=String(e.value||"");if(s.length>4096)throw new Error("Extension session value is too large.");return window.sessionStorage.setItem(t,s),{ok:!0}},async callApi(e){const t=String(e.method||"GET").toUpperCase(),s=String(e.path||"");if(!this.allowedApiRoute(t,s))throw new Error("Extension API route is not allowed.");const a={method:t,headers:{},credentials:"same-origin"};void 0!==e.body&&null!==e.body&&(a.headers["content-type"]="application/json",a.body=JSON.stringify(e.body));const i=await fetch(s,a),n=await i.text();let o=n;if(n)try{o=JSON.parse(n)}catch(e){o=n}if(!i.ok)throw new Error("object"==typeof o&&o.detail?o.detail:n);return this.rememberPaymentHashes(o),o},notify(e){const t=["positive","negative","warning","info"].includes(e.level)?e.level:"info";window.Quasar?.Notify&&window.Quasar.Notify.create({color:t,message:String(e.message||"")})},async scanQrCode(){if(!this.hasBridgePermission("ui.camera.scan_qr"))throw new Error("Extension is missing scanner permission.");if(!this.g)throw new Error("LNbits scanner is not available.");if(this.g.scanner)throw new Error("A scanner is already active.");if(await this.requireCameraScanApproval(),this.g.scanner)throw new Error("A scanner is already active.");return new Promise((e,t)=>{let s=!1;const a=()=>{window.clearTimeout(o),window.clearInterval(r),this.g.scanner===n&&(this.g.scanner=null)},i=e=>t=>{s||(s=!0,a(),e(t))},n=t=>{i(e)({value:String(t||"")})},o=window.setTimeout(()=>{i(t)(new Error("QR scan timed out."))},12e4),r=window.setInterval(()=>{s||this.g.scanner===n||i(t)(new Error("QR scan cancelled."))},250);this.g.scanner=n})},async requestBackgroundPaymentPermission(e){const t=await this.requestExtensionPermissions({permissions:[{id:"wallet.pay_invoice_background",grant:e?.grant||{}}]});return t.permissions?.[0]||t},async requestWalletPaymentWatchPermission(e){const t=await this.requestExtensionPermissions({permissions:[{id:"wallet.payments.watch",grant:e?.grant||{}}]});return t.permissions?.[0]||t},async requestExtensionPermissions(e){if(this.bridge.public)throw new Error("Public pages cannot request permissions.");const t=Array.isArray(e.permissions)?e.permissions:[];if(!t.length)throw new Error("No permissions requested.");const s=t.map(e=>this.normalizePermissionRequest(e)),a=await this.checkExtensionPermissions(s.map(e=>({id:e.id,grant:e.grant}))),i=Array.isArray(a?.permissions)?a.permissions:[],n=[],o=[];for(const[e,t]of s.entries()){const s=i[e]||{};if(s.id&&s.id!==t.id)throw new Error("Permission check response did not match request.");if(s.approved){n.push(t.label),o.push({id:t.id,approved:!0,grant:s.grant||t.grant});continue}const a=await this.promptExtensionPermission(t);o.push({id:t.id,approved:!0,grant:a?.grant||t.grant})}return this.notifyApprovedPermissionUse(n),{permissions:o}},normalizePermissionRequest(e){const t=String(e?.id||""),s=e?.grant||{};if("wallet.pay_invoice_background"===t)return this.normalizeBackgroundPaymentPermission(s);if("wallet.payments.watch"===t)return this.normalizeWalletPaymentWatchPermission(s);throw new Error(`Unsupported permission request: ${t}.`)},normalizeBackgroundPaymentPermission(e){if(!this.hasBridgePermission("wallet.pay_invoice_background"))throw new Error("Extension is missing background payment permission.");const t=String(e.walletId||e.wallet_id||""),s=this.walletById(t);if(!s)throw new Error("Selected wallet is not available.");if("lightning-shared"===s.walletType)throw new Error("Background payments are not allowed from shared wallets.");const a={wallet_id:t,max_amount:this.positiveInteger(e.maxAmount||e.max_amount,1e3),destination_policy:this.backgroundPaymentDestinationPolicy(e.destinationPolicy||e.destination_policy)};if(!a.max_amount)throw new Error("Max payment amount must be greater than zero.");return{id:"wallet.pay_invoice_background",grant:a,label:`Background payments from ${s.name||t}`,wallet:s}},normalizeWalletPaymentWatchPermission(e){if(!this.hasBridgePermission("wallet.payments.watch"))throw new Error("Extension is missing wallet payment watch permission.");const t=String(e.walletId||e.wallet_id||""),s=this.walletById(t);if(!s)throw new Error("Selected wallet is not available.");return{id:"wallet.payments.watch",grant:{wallet_id:t},label:`Watch wallet payments for ${s.name||t}`,wallet:s}},walletById(e){return(this.g?.user?.wallets||[]).find(t=>t.id===e)||null},async checkExtensionPermissions(e){return await this.postExtensionPermission("check",{permissions:e},"Could not check extension permissions.")},promptExtensionPermission(e){if("wallet.pay_invoice_background"===e.id)return this.promptBackgroundPaymentPermission(e);if("wallet.payments.watch"===e.id)return this.promptWalletPaymentWatchPermission(e);throw new Error(`Unsupported permission request: ${e.id}.`)},promptBackgroundPaymentPermission(e){if(this.backgroundPaymentPrompt.show)throw new Error("Background payment prompt is already open.");return new Promise((t,s)=>{this.backgroundPaymentPrompt={extensionName:this.extensionName||this.bridge.extensionId||"This extension",form:{destinationPolicy:e.grant.destination_policy,maxAmount:e.grant.max_amount},reject:s,resolve:t,show:!0,walletId:e.grant.wallet_id,walletName:e.wallet.name||e.grant.wallet_id}})},async resolveBackgroundPaymentPrompt(e){const t=this.backgroundPaymentPrompt;if(t.show){if(!e)return this.backgroundPaymentPrompt=this.emptyBackgroundPaymentPrompt(),void t.reject?.(new Error("Background payment permission denied."));try{const e={wallet_id:t.walletId,max_amount:this.positiveInteger(t.form.maxAmount,0),destination_policy:this.backgroundPaymentDestinationPolicy(t.form.destinationPolicy)};if(!e.max_amount)throw new Error("Max payment amount must be greater than zero.");const s=await this.postExtensionPermission("background-payment",e,"Could not save permission.");this.backgroundPaymentPrompt=this.emptyBackgroundPaymentPrompt(),t.resolve?.(s)}catch(e){t.reject?.(e),this.backgroundPaymentPrompt=this.emptyBackgroundPaymentPrompt()}}},rejectBackgroundPaymentPrompt(e){const t=this.backgroundPaymentPrompt.reject;this.backgroundPaymentPrompt=this.emptyBackgroundPaymentPrompt(),t?.(new Error(e))},promptWalletPaymentWatchPermission(e){if(this.walletPaymentWatchPrompt.show)throw new Error("Wallet payment watch prompt is already open.");return new Promise((t,s)=>{this.walletPaymentWatchPrompt={extensionName:this.extensionName||this.bridge.extensionId||"This extension",reject:s,resolve:t,show:!0,walletId:e.grant.wallet_id,walletName:e.wallet.name||e.grant.wallet_id}})},async resolveWalletPaymentWatchPrompt(e){const t=this.walletPaymentWatchPrompt;if(t.show){if(!e)return this.walletPaymentWatchPrompt=this.emptyWalletPaymentWatchPrompt(),void t.reject?.(new Error("Wallet payment watch permission denied."));try{const e=await this.postExtensionPermission("wallet-payments-watch",{wallet_id:t.walletId},"Could not save permission.");this.walletPaymentWatchPrompt=this.emptyWalletPaymentWatchPrompt(),t.resolve?.(e)}catch(e){t.reject?.(e),this.walletPaymentWatchPrompt=this.emptyWalletPaymentWatchPrompt()}}},rejectWalletPaymentWatchPrompt(e){const t=this.walletPaymentWatchPrompt.reject;this.walletPaymentWatchPrompt=this.emptyWalletPaymentWatchPrompt(),t?.(new Error(e))},positiveInteger(e,t){const s=Number(e);return!Number.isFinite(s)||s<=0?t:Math.floor(s)},backgroundPaymentDestinationPolicy:e=>"external_allowed"===e?"external_allowed":"own_wallets_only",async postExtensionPermission(e,t,s){const a=await fetch(`/api/v1/extension/${encodeURIComponent(this.bridge.extensionId)}/permissions/${e}`,{method:"POST",headers:{"content-type":"application/json"},credentials:"same-origin",body:JSON.stringify(t)}),i=await a.text();let n={};if(i)try{n=JSON.parse(i)}catch(e){n={detail:i}}if(!a.ok)throw new Error(n?.detail||s);return n},notifyApprovedPermissionUse(e){const t=e.filter(Boolean).join(", ");t&&this.notify({level:"info",message:`Using approved permissions: ${t}.`})},requireCameraScanApproval(){return this.isCameraScanRemembered()?Promise.resolve():this.cameraPrompt.show?Promise.reject(new Error("Camera access prompt is already open.")):new Promise((e,t)=>{this.cameraPrompt={extensionName:this.extensionName||this.bridge.extensionId||"This extension",reject:t,resolve:e,show:!0}})},isCameraScanRemembered(){try{return"allow"===this.$q.localStorage.getItem(this.cameraPromptStorageKey())}catch(e){return!1}},rememberCameraScanApproval(){try{this.$q.localStorage.set(this.cameraPromptStorageKey(),"allow")}catch(e){}},resolveCameraPrompt(e){const t=this.cameraPrompt.resolve,s=this.cameraPrompt.reject;if(this.cameraPrompt=this.emptyCameraPrompt(),"allow_remember"===e)return this.rememberCameraScanApproval(),void t?.();"allow"!==e?s?.(new Error("Camera scan denied by user.")):t?.()},rejectCameraPrompt(e){const t=this.cameraPrompt.reject;this.cameraPrompt=this.emptyCameraPrompt(),t?.(new Error(e))},rememberPaymentHashes(e){if(e&&"object"==typeof e)if(Array.isArray(e))e.forEach(e=>this.rememberPaymentHashes(e));else for(const[t,s]of Object.entries(e))["paymentHash","payment_hash"].includes(t)&&this.isPaymentHash(s)&&this.allowedPaymentHashes.add(s),this.rememberPaymentHashes(s)},isPaymentHash:e=>"string"==typeof e&&/^[a-f0-9]{64}$/i.test(e),isWebsocketItemId:e=>"string"==typeof e&&/^[A-Za-z0-9][A-Za-z0-9:_-]{0,127}$/.test(e),websocketUrl(e){const t=new URL(window.location.href);return t.protocol="https:"===t.protocol?"wss:":"ws:",t.pathname=e,t.search="",t.hash="",t.toString()},sendBridgeEvent(e){this.bridgePort&&this.bridgePort.postMessage({type:"lnbits-extension:event",...e})},closePaymentSubscription(e){const t=this.paymentSubscriptions.get(e);if(t){this.paymentSubscriptions.delete(e);try{t.socket.close()}catch(e){}}},closePaymentSubscriptions(){for(const e of Array.from(this.paymentSubscriptions.keys()))this.closePaymentSubscription(e)},closeWebsocketSubscription(e){const t=this.websocketSubscriptions.get(e);if(t){this.websocketSubscriptions.delete(e);try{t.socket.close()}catch(e){}}},closeWebsocketSubscriptions(){for(const e of Array.from(this.websocketSubscriptions.keys()))this.closeWebsocketSubscription(e)},closeBridgePort(){this.closePaymentSubscriptions(),this.closeWebsocketSubscriptions(),this.bridgePort?.close(),this.bridgePort=null},subscribePayment(e){const t=String(e.subscriptionId||""),s=String(e.paymentHash||"");if(!t||!this.isPaymentHash(s))throw new Error("Invalid payment subscription.");if(!this.allowedPaymentHashes.has(s))throw new Error("Payment subscription is not allowed.");this.closePaymentSubscription(t);const a=new WebSocket(this.websocketUrl(`/api/v1/ws/${encodeURIComponent(s)}`));this.paymentSubscriptions.set(t,{paymentHash:s,socket:a}),a.addEventListener("message",e=>{let a=e.data;try{a=JSON.parse(e.data)}catch(e){}this.sendBridgeEvent({event:"payment.update",subscriptionId:t,paymentHash:s,data:a}),a&&"object"==typeof a&&(!1===a.pending||["success","settled","paid"].includes(String(a.status||"")))&&(this.sendBridgeEvent({event:"payment.settled",subscriptionId:t,paymentHash:s,data:a}),this.closePaymentSubscription(t))}),a.addEventListener("error",()=>{this.sendBridgeEvent({event:"payment.error",subscriptionId:t,paymentHash:s}),this.closePaymentSubscription(t)}),a.addEventListener("close",()=>{this.paymentSubscriptions.delete(t)})},subscribeWebsocket(e){if(!this.hasBridgePermission("websocket.subscribe"))throw new Error("Extension is missing websocket subscribe permission.");const t=String(e.subscriptionId||""),s=String(e.itemId||"");if(!t||t.length>128||!this.isWebsocketItemId(s))throw new Error("Invalid websocket subscription.");this.closeWebsocketSubscription(t);const a=new WebSocket(this.websocketUrl(`/api/v1/ext/ws/${encodeURIComponent(this.bridge.extensionId)}/${encodeURIComponent(s)}`));this.websocketSubscriptions.set(t,{itemId:s,socket:a}),a.addEventListener("message",e=>{let a=e.data;try{a=JSON.parse(e.data)}catch(e){}this.sendBridgeEvent({event:"websocket.message",subscriptionId:t,itemId:s,data:a})}),a.addEventListener("error",()=>{this.sendBridgeEvent({event:"websocket.error",subscriptionId:t,itemId:s}),this.closeWebsocketSubscription(t)}),a.addEventListener("close",()=>{this.websocketSubscriptions.delete(t)})},sendWebsocket(e){if(!this.hasBridgePermission("websocket.subscribe"))throw new Error("Extension is missing websocket subscribe permission.");const t=String(e.subscriptionId||""),s=this.websocketSubscriptions.get(t);if(!s)throw new Error("Unknown websocket subscription.");if(s.socket.readyState!==WebSocket.OPEN)throw new Error("Websocket subscription is not open.");const a="string"==typeof e.data?e.data:JSON.stringify(e.data??{});s.socket.send(a)},async handleBridgeRequest(e,t){if(e&&"lnbits-extension:request"===e.type)try{if("context"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:this.plainBridgeContext()});if("api"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:await this.callApi(e)});if("ui.notify"===e.action)return this.notify(e),void this.sendResponse(t,e.id,{ok:!0,data:{ok:!0}});if("navigation.replace"===e.action)return await this.replaceExtensionRoute(e),void this.sendResponse(t,e.id,{ok:!0,data:{ok:!0}});if("navigation.open_new_tab"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:await this.openNewTab(e)});if("storage.session.get"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:this.getBridgeSessionValue(e)});if("storage.session.set"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:this.setBridgeSessionValue(e)});if("ui.scan_qr"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:await this.scanQrCode()});if("permissions.request"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:await this.requestExtensionPermissions(e)});if("permissions.request_background_payment"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:await this.requestBackgroundPaymentPermission(e)});if("permissions.request_wallet_payment_watch"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:await this.requestWalletPaymentWatchPermission(e)});if("payment.subscribe"===e.action)return this.subscribePayment(e),void this.sendResponse(t,e.id,{ok:!0,data:{ok:!0}});if("payment.unsubscribe"===e.action)return this.closePaymentSubscription(String(e.subscriptionId||"")),void this.sendResponse(t,e.id,{ok:!0,data:{ok:!0}});if("websocket.subscribe"===e.action)return this.subscribeWebsocket(e),void this.sendResponse(t,e.id,{ok:!0,data:{ok:!0}});if("websocket.unsubscribe"===e.action)return this.closeWebsocketSubscription(String(e.subscriptionId||"")),void this.sendResponse(t,e.id,{ok:!0,data:{ok:!0}});if("websocket.send"===e.action)return this.sendWebsocket(e),void this.sendResponse(t,e.id,{ok:!0,data:{ok:!0}});throw new Error("Unknown extension bridge action.")}catch(s){this.sendResponse(t,e.id,{ok:!1,error:s instanceof Error?s.message:String(s)})}},onWindowMessage(e){if(e.source!==this.extensionFrameWindow())return;const t=e.data;if(!t||"lnbits-extension:connect"!==t.type)return;const s=e.ports?.[0];s&&(this.closeBridgePort(),this.bridgePort=s,this.bridgePort.addEventListener("message",e=>{this.handleBridgeRequest(e.data,e=>{s.postMessage(e)})}),this.bridgePort.start(),this.bridgePort.postMessage({type:"lnbits-extension:connected",id:t.id}))}}};const quasarConfig={config:{loading:{spinner:Quasar.QSpinnerBars},table:{rowsPerPageOptions:[5,10,20,50,100,200,500,0]}}},DynamicComponent={async created(){const e=this.$route.path.split("/")[1],t=`/${e}/`,s=`/${e}/static/routes.json`;this.$router.getRoutes().some(e=>e.path===t)||this.$route.fullPath.startsWith("/extensions/builder/preview")||fetch(s).then(async e=>{if(!e.ok)throw new Error("No dynamic routes found");(await e.json()).forEach(e=>{console.log("Adding dynamic route:",e.path),window.router.addRoute({path:e.path,name:e.name,component:async()=>{if(await LNbits.utils.loadTemplate(e.template),e.i18n){const t=window.i18n?.global?.locale?.value??window.i18n?.global?.locale??window.g.locale??"en";await LNbits.utils.loadExtI18n(e.i18n,t)}return await LNbits.utils.loadScript(e.component),window[e.name]}}),window.router.push(this.$route.fullPath)})}).catch(()=>{let e=RENDERED_ROUTE;if(2===e.split("/").length&&(e+="/"),e!==this.$route.path)return console.log("Redirecting to non-vue route:",this.$route.fullPath),void(window.location=this.$route.fullPath)})}},routes=[{path:"/node",name:"Node",component:PageNode},{path:"/node/public",name:"NodePublic",component:PageNodePublic},{path:"/payments",name:"Payments",component:PagePayments},{path:"/audit",name:"Audit",component:PageAudit},{path:"/wallet",redirect:e=>{const t=window.g?.lastActiveWallet||window.g?.user?.wallets[0].id;return`/wallet/${e.query.wal||t||"default"}`}},{path:"/wallet/:id",name:"Wallet",component:PageWallet},{path:"/wallets",name:"Wallets",component:PageWallets},{path:"/users",name:"Users",component:PageUsers},{path:"/admin/extensions/wasm",name:"AdminWasmRuntime",component:PageAdmin},{path:"/admin/extensions/wasm/limits",name:"AdminWasmLimitConfig",component:PageAdmin},{path:"/admin/extensions/wasm/limits/:extId",name:"AdminWasmLimitConfigDetail",component:PageAdmin},{path:"/admin/extensions/wasm/:extId",name:"AdminWasmRuntimeDetail",component:PageAdmin},{path:"/admin",name:"Admin",component:PageAdmin},{path:"/account",name:"Account",component:PageAccount},{path:"/extensions/builder",name:"ExtensionsBuilder",component:PageExtensionBuilder},{path:"/extensions/builder/preview",name:"ExtensionsBuilderPreview",component:PageExtensionBuilderPreview},{path:"/extensions",name:"Extensions",component:PageExtensions},{path:"/first_install",name:"FirstInstall",component:PageFirstInstall},{path:"/",name:"PageHome",component:PageHome},{path:"/error",name:"PageError",component:PageError},{path:"/ext/:extId",name:"WasmExtensionRoot",component:window.WasmExtensionComponent},{path:"/ext/:extId/:pathMatch(.*)*",name:"WasmExtension",component:window.WasmExtensionComponent},{path:"/:pathMatch(.*)*",name:"DynamicComponent",component:DynamicComponent}];window.router=VueRouter.createRouter({history:VueRouter.createWebHistory(),routes:routes}),window.LOCALE=window.g.locale,window.i18n=new VueI18n.createI18n({locale:window.g.locale,fallbackLocale:"en",messages:window.localisation}),function(){let e=!1,t=null;Vue.watch(()=>window.i18n.global.locale,async(s,a)=>{!e&&LNbits.utils._extI18nDirs.size&&(t=s,e=!0,window.i18n.global.locale=a,e=!1,await Promise.all([...LNbits.utils._extI18nDirs].map(e=>LNbits.utils.loadExtI18n(e,s))),t===s&&(e=!0,window.i18n.global.locale=s,e=!1))},{flush:"sync"})}(),window.app.mixin({data:()=>({api:window._lnbitsApi,utils:window._lnbitsUtils,g:window.g}),methods:{copyText:window._lnbitsUtils.copyText,formatBalance:window._lnbitsUtils.formatBalance}}),window.app.use(VueQrcodeReader),window.app.use(Quasar,quasarConfig),window.app.use(window.i18n),window.app.use(window.router),window.app.mount("#vue"); \ No newline at end of file +window.PageError={template:"#page-error"},window.PageHome={template:"#page-home",data:()=>({lnurl:"",authAction:"login",authMethod:"username-password",usr:"",username:"",reset_key:"",email:"",password:"",passwordRepeat:"",invitationCode:"",walletName:"",signup:!1}),computed:{showClaimLnurl(){return""!==this.lnurl&&this.g.settings.allowRegister&&this.g.settings.authMethods.includes("user-id-only")},formatDescription(){return LNbits.utils.convertMarkdown(this.g.settings.siteDescription)},isAccessTokenExpired(){return this.$q.cookies.get("is_access_token_expired")}},methods:{showLogin(e){this.authAction="login",this.authMethod=e},showRegister(e){this.user="",this.username=null,this.password=null,this.passwordRepeat=null,this.invitationCode=null,this.authAction="register",this.authMethod=e},async register(){try{await LNbits.api.register(this.username,this.email,this.password,this.passwordRepeat,this.invitationCode),this.refreshAuthUser()}catch(e){LNbits.utils.notifyApiError(e)}},async reset(){try{await LNbits.api.reset(this.reset_key,this.password,this.passwordRepeat),this.refreshAuthUser()}catch(e){LNbits.utils.notifyApiError(e)}},async login(){try{await LNbits.api.login(this.username,this.password),this.refreshAuthUser()}catch(e){LNbits.utils.notifyApiError(e)}},async loginUsr(){try{await LNbits.api.loginUsr(this.usr),this.refreshAuthUser()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}},async refreshAuthUser(){try{const e=await LNbits.api.getAuthUser();this.g.user=LNbits.map.user(e.data),this.g.isPublicPage=!1,this.$router.push(`/wallet/${this.g.user.wallets[0].id}`)}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}},createWallet(){LNbits.api.createAccount(this.walletName).then(e=>{this.$router.push(`/wallet/${e.data.id}`)})},processing(){Quasar.Notify.create({timeout:0,message:"Processing...",icon:null})}},created(){if(this.g.isUserAuthorized)return this.refreshAuthUser();const e=new URLSearchParams(window.location.search);this.reset_key=e.get("reset_key"),this.reset_key&&(this.authAction="reset"),e.has("lightning")&&(this.lnurl=e.get("lightning"))}},window.PageExtensionBuilder={template:"#page-extension-builder",data:()=>({step:1,previewStepNames:{2:"settings",3:"owner_data",4:"client_data",5:"public_page"},extensionDataCleanString:"",extensionData:{id:"",name:"",stub_version:"",short_description:"",description:"",public_page:{has_public_page:!0,owner_data_fields:{name:"",description:""},client_data_fields:{public_inputs:[]},action_fields:{generate_action:!0,generate_payment_logic:!1,wallet_id:"",currency:"",amount:"",paid_flag:""}},preview_action:{is_preview_mode:!1,is_settings_preview:!1,is_owner_data_preview:!1,is_client_data_preview:!1,is_public_page_preview:!1},settings_data:{name:"Settings",enabled:!0,type:"user",fields:[]},owner_data:{name:"OwnerData",fields:[]},client_data:{enabled:!0,name:"ClientData",fields:[]}},sampleField:{name:"name",type:"str",label:"Name",hint:"",optional:!0,editable:!0,searchable:!0,sortable:!0},settingsTypes:[{label:"User Settings",value:"user"},{label:"Admin Settings",value:"admin"}],amountSource:[{label:"Client Data",value:"client_data"},{label:"Owner Data",value:"owner_data"}],extensionStubVersions:[]}),watch:{"extensionData.public_page.action_fields.amount_source":function(e,t){t&&e!==t&&(this.extensionData.public_page.action_fields.amount="")}},computed:{paymentActionAmountFields(){const e=this.extensionData.public_page.action_fields.amount_source;return e?"owner_data"===e?[""].concat(this.extensionData.owner_data.fields.filter(e=>"int"===e.type||"float"===e.type).map(e=>e.name)):"client_data"===e?[""].concat(this.extensionData.client_data.fields.filter(e=>"int"===e.type||"float"===e.type).map(e=>e.name)):void 0:[""]}},methods:{saveState(){this.$q.localStorage.set("lnbits.extension.builder.data",JSON.stringify(this.extensionData)),this.$q.localStorage.set("lnbits.extension.builder.step",this.step)},nextStep(){this.saveState(),this.$refs.stepper.next(),this.refreshPreview()},previousStep(){this.saveState(),this.$refs.stepper.previous(),this.refreshPreview()},onStepChange(){this.saveState(),this.refreshPreview()},clearAllData(){LNbits.utils.confirmDialog("Are you sure you want to clear all data? This action cannot be undone.").onOk(()=>{this.extensionData=JSON.parse(this.extensionDataCleanString),this.$q.localStorage.remove("lnbits.extension.builder.data"),this.$refs.stepper.set(1)})},exportJsonData(){!0!==Quasar.exportFile(`${this.extensionData.id||"data-export"}.json`,JSON.stringify(this.extensionData,null,2),"text/json")?Quasar.Notify.create({message:"Browser denied file download...",color:"negative",icon:null}):Quasar.Notify.create({message:"File downloaded!",color:"positive",icon:"file_download"})},onJsonDataInput(e){const t=e.target.files[0],s=new FileReader;s.onload=e=>{this.extensionData={...this.extensionData,...JSON.parse(e.target.result)},this.$refs.extensionDataInput.value=null,Quasar.Notify.create({message:"File loaded!",color:"positive",icon:"file_upload"})},s.readAsText(t)},async buildExtension(){try{const e={responseType:"blob"},t=await LNbits.api.request("POST","/api/v1/extension/builder/zip",null,this.extensionData,e),s=window.URL.createObjectURL(new Blob([t.data])),a=document.createElement("a");a.href=s,a.download=`${this.extensionData.id||"lnbits-extension"}.zip`,document.body.appendChild(a),a.click(),a.remove(),window.URL.revokeObjectURL(s)}catch(e){LNbits.utils.notifyApiError(e)}},async buildExtensionAndDeploy(){try{const{data:e}=await LNbits.api.request("POST","/api/v1/extension/builder/deploy",null,this.extensionData);Quasar.Notify.create({message:e.message||"Extension deployed!",color:"positive"})}catch(e){LNbits.utils.notifyApiError(e)}},async cleanCacheData(){LNbits.utils.confirmDialog("Are you sure you want to clean the cache data? This action cannot be undone.","Clean Cache Data").onOk(async()=>{try{const{data:e}=await LNbits.api.request("DELETE","/api/v1/extension/builder",null,{});Quasar.Notify.create({message:e.message||"Cache data cleaned!",color:"positive"})}catch(e){LNbits.utils.notifyApiError(e)}})},async previewExtension(e){this.saveState();try{await LNbits.api.request("POST","/api/v1/extension/builder/preview",null,{...this.extensionData,preview_action:{is_preview_mode:!!e,is_settings_preview:"settings"===e,is_owner_data_preview:"owner_data"===e,is_client_data_preview:"client_data"===e,is_public_page_preview:"public_page"===e}}),this.refreshIframe(e)}catch(e){LNbits.utils.notifyApiError(e)}},async refreshPreview(){setTimeout(()=>{const e=this.previewStepNames[`${this.step}`]||"";e&&this.previewExtension(e)},100)},async getStubExtensionReleases(){try{const e="extension_builder_stub",{data:t}=await LNbits.api.request("GET",`/api/v1/extension/${e}/releases`);this.extensionStubVersions=t;const s=t.filter(e=>e.is_version_compatible);this.extensionData.stub_version=s[0]?s[0].version:""}catch(e){LNbits.utils.notifyApiError(e)}},refreshIframe(e=""){const t=this.$refs[`iframeStep${this.step}`];if(!t)return void console.warn("Extension Builder Preview iframe not loaded yet.");t.onload=()=>{const e=t.contentDocument||t.contentWindow.document;e.body.style.transform="scale(0.8)",e.body.style.transformOrigin="center top"};let s="Page"+this.extensionData.id.toLowerCase().split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("");"public_page"===e&&(s+="Public"),t.src=`/extensions/builder/preview?ext_id=${this.extensionData.id}&page=${e}&component=${s}`},initBasicData(){this.extensionData.owner_data.fields=[JSON.parse(JSON.stringify(this.sampleField))],this.extensionData.client_data.fields=[JSON.parse(JSON.stringify(this.sampleField))],this.extensionData.settings_data.fields=[JSON.parse(JSON.stringify(this.sampleField))],this.extensionDataCleanString=JSON.stringify(this.extensionData)}},created(){this.initBasicData();const e=this.$q.localStorage.getItem("lnbits.extension.builder.data");e&&(this.extensionData={...this.extensionData,...JSON.parse(e)});const t=+this.$q.localStorage.getItem("lnbits.extension.builder.step");t&&(this.step=t),this.g.user.admin&&this.getStubExtensionReleases(),setTimeout(()=>{this.refreshIframe()},1e3)}},window.PageExtensionBuilder={template:"#page-extension-builder",data:()=>({step:1,previewStepNames:{2:"settings",3:"owner_data",4:"client_data",5:"public_page"},extensionDataCleanString:"",extensionData:{id:"",name:"",stub_version:"",short_description:"",description:"",public_page:{has_public_page:!0,owner_data_fields:{name:"",description:""},client_data_fields:{public_inputs:[]},action_fields:{generate_action:!0,generate_payment_logic:!1,wallet_id:"",currency:"",amount:"",paid_flag:""}},preview_action:{is_preview_mode:!1,is_settings_preview:!1,is_owner_data_preview:!1,is_client_data_preview:!1,is_public_page_preview:!1},settings_data:{name:"Settings",enabled:!0,type:"user",fields:[]},owner_data:{name:"OwnerData",fields:[]},client_data:{enabled:!0,name:"ClientData",fields:[]}},sampleField:{name:"name",type:"str",label:"Name",hint:"",optional:!0,editable:!0,searchable:!0,sortable:!0},settingsTypes:[{label:"User Settings",value:"user"},{label:"Admin Settings",value:"admin"}],amountSource:[{label:"Client Data",value:"client_data"},{label:"Owner Data",value:"owner_data"}],extensionStubVersions:[]}),watch:{"extensionData.public_page.action_fields.amount_source":function(e,t){t&&e!==t&&(this.extensionData.public_page.action_fields.amount="")}},computed:{paymentActionAmountFields(){const e=this.extensionData.public_page.action_fields.amount_source;return e?"owner_data"===e?[""].concat(this.extensionData.owner_data.fields.filter(e=>"int"===e.type||"float"===e.type).map(e=>e.name)):"client_data"===e?[""].concat(this.extensionData.client_data.fields.filter(e=>"int"===e.type||"float"===e.type).map(e=>e.name)):void 0:[""]}},methods:{saveState(){this.$q.localStorage.set("lnbits.extension.builder.data",JSON.stringify(this.extensionData)),this.$q.localStorage.set("lnbits.extension.builder.step",this.step)},nextStep(){this.saveState(),this.$refs.stepper.next(),this.refreshPreview()},previousStep(){this.saveState(),this.$refs.stepper.previous(),this.refreshPreview()},onStepChange(){this.saveState(),this.refreshPreview()},clearAllData(){LNbits.utils.confirmDialog("Are you sure you want to clear all data? This action cannot be undone.").onOk(()=>{this.extensionData=JSON.parse(this.extensionDataCleanString),this.$q.localStorage.remove("lnbits.extension.builder.data"),this.$refs.stepper.set(1)})},exportJsonData(){!0!==Quasar.exportFile(`${this.extensionData.id||"data-export"}.json`,JSON.stringify(this.extensionData,null,2),"text/json")?Quasar.Notify.create({message:"Browser denied file download...",color:"negative",icon:null}):Quasar.Notify.create({message:"File downloaded!",color:"positive",icon:"file_download"})},onJsonDataInput(e){const t=e.target.files[0],s=new FileReader;s.onload=e=>{this.extensionData={...this.extensionData,...JSON.parse(e.target.result)},this.$refs.extensionDataInput.value=null,Quasar.Notify.create({message:"File loaded!",color:"positive",icon:"file_upload"})},s.readAsText(t)},async buildExtension(){try{const e={responseType:"blob"},t=await LNbits.api.request("POST","/api/v1/extension/builder/zip",null,this.extensionData,e),s=window.URL.createObjectURL(new Blob([t.data])),a=document.createElement("a");a.href=s,a.download=`${this.extensionData.id||"lnbits-extension"}.zip`,document.body.appendChild(a),a.click(),a.remove(),window.URL.revokeObjectURL(s)}catch(e){LNbits.utils.notifyApiError(e)}},async buildExtensionAndDeploy(){try{const{data:e}=await LNbits.api.request("POST","/api/v1/extension/builder/deploy",null,this.extensionData);Quasar.Notify.create({message:e.message||"Extension deployed!",color:"positive"})}catch(e){LNbits.utils.notifyApiError(e)}},async cleanCacheData(){LNbits.utils.confirmDialog("Are you sure you want to clean the cache data? This action cannot be undone.","Clean Cache Data").onOk(async()=>{try{const{data:e}=await LNbits.api.request("DELETE","/api/v1/extension/builder",null,{});Quasar.Notify.create({message:e.message||"Cache data cleaned!",color:"positive"})}catch(e){LNbits.utils.notifyApiError(e)}})},async previewExtension(e){this.saveState();try{await LNbits.api.request("POST","/api/v1/extension/builder/preview",null,{...this.extensionData,preview_action:{is_preview_mode:!!e,is_settings_preview:"settings"===e,is_owner_data_preview:"owner_data"===e,is_client_data_preview:"client_data"===e,is_public_page_preview:"public_page"===e}}),this.refreshIframe(e)}catch(e){LNbits.utils.notifyApiError(e)}},async refreshPreview(){setTimeout(()=>{const e=this.previewStepNames[`${this.step}`]||"";e&&this.previewExtension(e)},100)},async getStubExtensionReleases(){try{const e="extension_builder_stub",{data:t}=await LNbits.api.request("GET",`/api/v1/extension/${e}/releases`);this.extensionStubVersions=t;const s=t.filter(e=>e.is_version_compatible);this.extensionData.stub_version=s[0]?s[0].version:""}catch(e){LNbits.utils.notifyApiError(e)}},refreshIframe(e=""){const t=this.$refs[`iframeStep${this.step}`];if(!t)return void console.warn("Extension Builder Preview iframe not loaded yet.");t.onload=()=>{const e=t.contentDocument||t.contentWindow.document;e.body.style.transform="scale(0.8)",e.body.style.transformOrigin="center top"};let s="Page"+this.extensionData.id.toLowerCase().split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("");"public_page"===e&&(s+="Public"),t.src=`/extensions/builder/preview?ext_id=${this.extensionData.id}&page=${e}&component=${s}`},initBasicData(){this.extensionData.owner_data.fields=[JSON.parse(JSON.stringify(this.sampleField))],this.extensionData.client_data.fields=[JSON.parse(JSON.stringify(this.sampleField))],this.extensionData.settings_data.fields=[JSON.parse(JSON.stringify(this.sampleField))],this.extensionDataCleanString=JSON.stringify(this.extensionData)}},created(){this.initBasicData();const e=this.$q.localStorage.getItem("lnbits.extension.builder.data");e&&(this.extensionData={...this.extensionData,...JSON.parse(e)});const t=+this.$q.localStorage.getItem("lnbits.extension.builder.step");t&&(this.step=t),this.g.user.admin&&this.getStubExtensionReleases(),setTimeout(()=>{this.refreshIframe()},1e3)}},window.PageExtensionBuilderPreview={template:"#page-extension-builder-preview",mixins:[windowMixin],watch:{name:"reload"},data:()=>({extId:"",pageName:"",componentName:null}),methods:{async reload(){await LNbits.utils.loadTemplate(`/extensions/builder/preview/${this.extId}/template?page_name=${this.pageName}`),await LNbits.utils.loadScript(`/extensions/builder/preview/${this.extId}/component?page_name=${this.pageName}`),this._component=window[this.componentName],console.log("LNbits preview reloaded componentName:",this.componentName,!!this._component),this.$forceUpdate()}},async created(){const e=new URLSearchParams(window.location.search);this.extId=e.get("ext_id")||"",this.pageName=e.get("page")||"",this.componentName=e.get("component")||"",await this.reload()},render(){return this._component?Vue.h(this._component):Vue.h("div","Loading...")}};const EXTENSION_PERMISSION_DEFAULT_MAX_ROWS_PER_SOURCE=1e4,EXTENSION_PERMISSION_MAX_ROWS_PER_SOURCE_LIMIT=1e6,EXTENSION_PERMISSION_MAX_MESSAGES_PER_SECOND_LIMIT=100;window.PageExtensions={template:"#page-extensions",data(){return{extbuilderEnabled:!1,slide:0,fullscreen:!1,autoplay:!0,searchTerm:"",tab:"installed",manageExtensionTab:"releases",filteredExtensions:[],categories:new Set,updatableExtensions:[],showUninstallDialog:!1,showManageExtensionDialog:!1,showExtensionDetailsDialog:!1,showDropDbDialog:!1,showPayToEnableDialog:!1,showUpdateAllDialog:!1,dropDbExtensionId:"",selectedExtension:null,selectedImage:null,selectedExtensionDetails:null,selectedExtensionRepos:null,selectedRelease:null,permissionGrant:{show:!1,permissions:[],resolve:null},extensionPermissionMaxRowsPerSourceLimit:1e6,extensionPermissionMaxMessagesPerSecondLimit:100,managedExtensionPermissions:{loading:!1,extensionPermissions:[],userPermissions:{},savingExtensionPermissions:!1,savingKey:"",deletingKey:""},backgroundPaymentDestinationOptions:[{label:"Only transfers to my wallets",value:"own_wallets_only"},{label:"Allow external payments",value:"external_allowed"}],uninstallAndDropDb:!1,maxStars:5,paylinkWebsocket:null,searchToggle:!1,reviewsUrl:null,reviewsDialog:{show:!1,extension:null,loading:!1,submitting:!1,form:{name:"",rating:0,comment:""},error:null},reviews:[],reviewsTable:{loading:!1,columns:[{name:"name",align:"left",label:this.$t("Name"),field:"name",sortable:!0},{name:"comment",align:"left",label:this.$t("Comment"),field:"comment"},{name:"created_at",align:"left",label:this.$t("Date"),field:"created_at"},{name:"rating",align:"right",label:"Rating",field:"rating"}],pagination:{rowsPerPage:5,sortBy:"created_at",descending:!0,page:1}},paymentDialog:{show:!1,invoice:"",hash:""}}},watch:{searchTerm(e){this.filterExtensions(e,this.tab)},tab(e){this.filterExtensions(this.searchTerm,e)}},computed:{managedUserPermissionRows(){const e=[],t=this.managedExtensionPermissions.userPermissions||{};return Object.entries(t).forEach(([t,s])=>{Array.isArray(s)&&s.forEach(s=>{if(!s||"object"!=typeof s)return;const a=String(s.id||""),i=String(s.wallet_id||"");a&&i&&e.push({key:a,permissionId:t,label:this.permissionLabelById(t),grantId:a,walletId:i,walletName:this.walletName(i),grant:s})})}),e}},methods:{filterExtensions(e,t){const s=!["installed","all","featured"].includes(t);var a;this.filteredExtensions=this.extensions.filter(e=>"all"!==t||!e.isInstalled).filter(e=>"installed"!==t||e.isInstalled).filter(e=>"installed"!==t||(!!e.isActive||!!this.g.user.admin)).filter(e=>"featured"!==t||e.isFeatured).filter(e=>!s||(e=>e.categories?.includes(t)??!1)(e)).filter((a=e,function(e){return e.name.toLowerCase().includes(a.toLowerCase())||e.shortDescription?.toLowerCase().includes(a.toLowerCase())})).map(e=>({...e,details_link:e.installedRelease?.details_link||e.latestRelease?.details_link}))},async installExtension(e){this.unsubscribeFromPaylinkWs();const t=await this.resolveExtensionPermissionGrant(e);null!==t&&(this.selectedExtension.inProgress=!0,this.showManageExtensionDialog=!1,e.payment_hash=e.payment_hash||this.getPaylinkHash(e.pay_link),LNbits.api.request("POST","/api/v1/extension",this.g.user.wallets[0].adminkey,{ext_id:this.selectedExtension.id,archive:e.archive,source_repo:e.source_repo,payment_hash:e.payment_hash,version:e.version,permissions:t}).then(t=>{this.selectedExtension.inProgress=!1;const s=this.extensions.find(e=>e.id===this.selectedExtension.id);s.isAvailable=!0,s.isInstalled=!0,s.isWasm=!0===t.data.is_wasm||!0===t.data.isWasm||"wasm"===e.extension_type||!0===s.isWasm,s.icon=t.data.icon||s.icon,s.installedRelease=e,this.toggleExtension(s),s.inProgress=!1,this.selectedExtension=s,this.extensions=this.extensions.concat([]),this.tab="installed"}).catch(e=>{console.warn(e),this.selectedExtension.inProgress=!1,LNbits.utils.notifyApiError(e)}))},async uninstallExtension(){this.showManageExtensionDialog=!1,this.showUninstallDialog=!1,this.selectedExtension.inProgress=!0,LNbits.api.request("DELETE",`/api/v1/extension/${this.selectedExtension.id}`,this.g.user.wallets[0].adminkey).then(e=>{const t=this.extensions.find(e=>e.id===this.selectedExtension.id);t.isAvailable=!1,t.isInstalled=!1,t.inProgress=!1,t.installedRelease=null,this.filteredExtensions=this.filteredExtensions.filter(e=>e.id!==t.id),Quasar.Notify.create({type:"positive",message:"Extension uninstalled!"}),this.uninstallAndDropDb&&this.showDropDb()}).catch(e=>{LNbits.utils.notifyApiError(e),extension.inProgress=!1})},async dropExtensionDb(){const e=this.selectedExtension;this.showManageExtensionDialog=!1,this.showDropDbDialog=!1,this.dropDbExtensionId="",e.inProgress=!0,LNbits.api.request("DELETE",`/api/v1/extension/${e.id}/db`,this.g.user.wallets[0].adminkey).then(t=>{e.installedRelease=null,e.inProgress=!1,e.hasDatabaseTables=!1,Quasar.Notify.create({type:"positive",message:"Extension DB deleted!"})}).catch(t=>{LNbits.utils.notifyApiError(t),e.inProgress=!1})},toggleExtension(e){const t=e.isActive?"activate":"deactivate";LNbits.api.request("PUT",`/api/v1/extension/${e.id}/${t}`,this.g.user.wallets[0].adminkey).then(s=>{Quasar.Notify.create({timeout:2e3,type:"positive",message:`Extension '${e.id}' ${t}d!`})}).catch(t=>{LNbits.utils.notifyApiError(t),e.isActive=!1,e.inProgress=!1})},async enableExtensionForUser(e){e.isPaymentRequired?this.showPayToEnable(e):this.enableExtension(e)},async enableExtension(e){LNbits.api.request("PUT",`/api/v1/extension/${e.id}/enable`,this.g.user.wallets[0].adminkey).then(t=>{this.g.user.extensions=this.g.user.extensions.concat([e.id]),Quasar.Notify.create({type:"positive",message:"Extension enabled!"})}).catch(e=>{console.warn(e),LNbits.utils.notifyApiError(e)})},disableExtension(e){LNbits.api.request("PUT",`/api/v1/extension/${e.id}/disable`,this.g.user.wallets[0].adminkey).then(t=>{this.g.user.extensions=this.g.user.extensions.filter(t=>t!==e.id),Quasar.Notify.create({type:"positive",message:"Extension disabled!"})}).catch(e=>{console.warn(error),LNbits.utils.notifyApiError(e)})},showPayToEnable(e){this.selectedExtension=e,this.selectedExtension.payToEnable.paidAmount=e.payToEnable.amount,this.selectedExtension.payToEnable.showQRCode=!1,this.showPayToEnableDialog=!0},updatePayToInstallData(e){LNbits.api.request("PUT",`/api/v1/extension/${e.id}/sell`,this.g.user.wallets[0].adminkey,{required:e.payToEnable.required,amount:e.payToEnable.amount,wallet:e.payToEnable.wallet}).then(e=>{Quasar.Notify.create({type:"positive",message:"Payment info updated!"}),this.showManageExtensionDialog=!1}).catch(t=>{LNbits.utils.notifyApiError(t),e.inProgress=!1})},showUninstall(){this.showManageExtensionDialog=!1,this.showUninstallDialog=!0,this.uninstallAndDropDb=!1},showDropDb(){this.showDropDbDialog=!0},async showManageExtension(e){if(this.selectedExtension=e,this.selectedRelease=null,this.selectedExtensionRepos=null,this.resetManagedExtensionPermissions(),this.manageExtensionTab=this.g.user.admin?"releases":"extension-permissions",this.showManageExtensionDialog=!0,this.canManageExtensionPermissions(e)&&this.loadManagedExtensionPermissions(e),this.g.user.admin)try{const{data:t}=await LNbits.api.request("GET",`/api/v1/extension/${e.id}/releases`);this.selectedExtensionRepos=t.reduce((e,t)=>(e[t.source_repo]=e[t.source_repo]||{releases:[],isInstalled:!1,repo:t.repo},t.inProgress=!1,t.error=null,t.loaded=!1,t.isInstalled=this.isInstalledVersion(this.selectedExtension,t),t.isInstalled&&(e[t.source_repo].isInstalled=!0),t.pay_link&&(t.requiresPayment=!0,t.paidAmount=t.cost_sats,t.payment_hash=this.getPaylinkHash(t.pay_link)),e[t.source_repo].releases.push(t),e),{})}catch(t){LNbits.utils.notifyApiError(t),e.inProgress=!1}},canShowManageExtensionButton(e){return this.g.user.admin||!0===e?.isWasm&&!0===e?.isInstalled},canManageExtensionPermissions(e=this.selectedExtension){return!0===e?.isWasm&&!0===e?.isInstalled},canShowAdminManageTabs(){return!0===this.g.user.admin},resetManagedExtensionPermissions(){this.managedExtensionPermissions={loading:!1,extensionPermissions:[],userPermissions:{},savingExtensionPermissions:!1,savingKey:"",deletingKey:""}},async loadManagedExtensionPermissions(e=this.selectedExtension){if(this.canManageExtensionPermissions(e)){this.managedExtensionPermissions.loading=!0;try{const{data:t}=await LNbits.api.request("GET",`/api/v1/extension/${e.id}/permissions`);this.managedExtensionPermissions.extensionPermissions=this.cloneEditableExtensionPermissions(t.extension_permissions||[]),this.managedExtensionPermissions.userPermissions=this.cloneUserPermissions(t.user_permissions||{})}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}finally{this.managedExtensionPermissions.loading=!1}}},cloneEditableExtensionPermissions(e){return(e||[]).filter(e=>e&&"object"==typeof e).map(e=>({...e,policies:Array.isArray(e.policies)?e.policies.map(t=>this.cloneEditablePermissionPolicy(e.id,t)):e.policies}))},cloneEditablePermissionPolicy(e,t){if(!t||"object"!=typeof t||Array.isArray(t))return t;const s=Object.entries(t).reduce((e,[t,s])=>({...e,[t]:Array.isArray(s)?s.slice():s}),{});return"ext.storage.append_public"===e&&(s.max_rows_per_source=this.maxRowsPerSourceValue(s.max_rows_per_source,1e4)),"websocket.publish"===e&&(s.max_messages_per_second=Number(s.max_messages_per_second)),s},maxRowsPerSourceValue(e,t){const s=Number(e);return!Number.isInteger(s)||s<=0?t:Math.min(s,1e6)},extensionPermissionLimitError(e){const t=(e||[]).find(e=>"ext.storage.append_public"===e?.id);if(!t||!Array.isArray(t.policies))return this.websocketPublishLimitError(e);for(const e of t.policies){if(!e||"object"!=typeof e)continue;const t=Number(e.max_rows_per_source);if(!Number.isInteger(t)||t<=0)return"Max rows per source must be a positive integer.";if(t>1e6)return"Max rows per source cannot exceed 1000000."}return this.websocketPublishLimitError(e)},websocketPublishLimitError(e){const t=(e||[]).find(e=>"websocket.publish"===e?.id);if(!t)return"";if(!Array.isArray(t.policies)||1!==t.policies.length)return"Websocket publish requires a max messages per second policy.";const s=t.policies[0];if(!s||"object"!=typeof s)return"Websocket publish requires a max messages per second policy.";const a=Number(s.max_messages_per_second);return!Number.isInteger(a)||a<=0?"Max messages per second must be a positive integer.":a>100?"Max messages per second cannot exceed 100.":""},validateExtensionPermissionLimits(e){const t=this.extensionPermissionLimitError(e);return!t||(Quasar.Notify.create({type:"negative",message:t}),!1)},extensionPermissionsHaveEditableLimits:e=>(e||[]).some(e=>"ext.storage.append_public"===e?.id&&Array.isArray(e.policies)&&e.policies.length>0||"websocket.publish"===e?.id),async saveManagedExtensionPermissions(){const e=this.managedExtensionPermissions.extensionPermissions;if(this.validateExtensionPermissionLimits(e)){this.managedExtensionPermissions.savingExtensionPermissions=!0;try{const{data:t}=await LNbits.api.request("PUT",`/api/v1/extension/${this.selectedExtension.id}/permissions`,this.g.user.wallets[0].adminkey,{permissions:this.cloneEditableExtensionPermissions(e)});this.managedExtensionPermissions.extensionPermissions=this.cloneEditableExtensionPermissions(t.extension_permissions||[]),Quasar.Notify.create({type:"positive",message:"Permission updated."})}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}finally{this.managedExtensionPermissions.savingExtensionPermissions=!1}}},cloneUserPermissions(e){const t={};return Object.entries(e||{}).forEach(([e,s])=>{Array.isArray(s)&&(t[e]=s.filter(e=>e&&"object"==typeof e).map(e=>({...e,_original:{...e}})))}),t},async showExtensionDetails(e,t){if(t){this.selectedExtension=this.extensions.find(t=>t.id===e)||this.selectedExtension,this.selectedExtensionDetails=null,this.showExtensionDetailsDialog=!0,this.slide=0,this.fullscreen=!1;try{const{data:s}=await LNbits.api.request("GET",`/api/v1/extension/${e}/details?details_link=${t}`);this.selectedExtensionDetails=s,this.selectedExtensionDetails.description_md=LNbits.utils.convertMarkdown(s.description_md)}catch(e){console.warn(e)}}},async payAndInstall(e){try{if(null===await this.resolveExtensionPermissionGrant(e))return;this.selectedExtension.inProgress=!0,this.showManageExtensionDialog=!1;const t=await this.requestPaymentForInstall(this.selectedExtension.id,e);this.rememberPaylinkHash(e.pay_link,t.payment_hash);const s=this.g.user.wallets.find(t=>t.id===e.wallet),{data:a}=await LNbits.api.payInvoice(s,t.payment_request);e.payment_hash=a.payment_hash,await this.installExtension(e)}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}finally{this.selectedExtension.inProgress=!1}},async payAndEnable(e){try{const t=await this.requestPaymentForEnable(e.id,e.payToEnable.paidAmount),s=this.g.user.wallets.find(t=>t.id===e.payToEnable.paymentWallet),{data:a}=await LNbits.api.payInvoice(s,t.payment_request);this.enableExtension(e),this.showPayToEnableDialog=!1}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}},async showInstallQRCode(e){if(null!==await this.resolveExtensionPermissionGrant(e)){this.selectedRelease=e;try{const t=await this.requestPaymentForInstall(this.selectedExtension.id,e);this.selectedRelease.paymentRequest=t.payment_request,this.selectedRelease.payment_hash=t.payment_hash,this.selectedRelease=_.clone(this.selectedRelease),this.rememberPaylinkHash(this.selectedRelease.pay_link,this.selectedRelease.payment_hash),this.subscribeToPaylinkWs(this.selectedRelease.pay_link,t.payment_hash)}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}}},async showEnableQRCode(e){try{e.payToEnable.showQRCode=!0,this.selectedExtension=_.clone(e);const t=await this.requestPaymentForEnable(e.id,e.payToEnable.paidAmount);e.payToEnable.paymentRequest=t.payment_request,this.selectedExtension=_.clone(e);const s=new URL(window.location);s.protocol="https:"===s.protocol?"wss":"ws",s.pathname=`/api/v1/ws/${t.payment_hash}`;const a=new WebSocket(s);a.addEventListener("message",async({data:t})=>{!1===JSON.parse(t).pending&&(Quasar.Notify.create({type:"positive",message:"Invoice Paid!"}),this.enableExtension(e),a.close())})}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}},async requestPaymentForInstall(e,t){const{data:s}=await LNbits.api.request("PUT",`/api/v1/extension/${e}/invoice/install`,null,{ext_id:e,archive:t.archive,source_repo:t.source_repo,cost_sats:t.paidAmount,version:t.version});return s},async requestPaymentForEnable(e,t){const{data:s}=await LNbits.api.request("PUT",`/api/v1/extension/${e}/invoice/enable`,null,{amount:t});return s},clearHangingInvoice(e){this.forgetPaylinkHash(e.pay_link),e.payment_hash=null},rememberPaylinkHash(e,t){this.$q.localStorage.set(`lnbits.extensions.paylink.${e}`,t)},getPaylinkHash(e){return this.$q.localStorage.getItem(`lnbits.extensions.paylink.${e}`)},forgetPaylinkHash(e){this.$q.localStorage.remove(`lnbits.extensions.paylink.${e}`)},subscribeToPaylinkWs(e,t){const s=new URL(`${e}/${t}`);s.protocol="https:"===s.protocol?"wss":"ws",this.paylinkWebsocket=new WebSocket(s),this.paylinkWebsocket.addEventListener("message",async({data:e})=>{JSON.parse(e).paid?(Quasar.Notify.create({type:"positive",message:"Invoice Paid!"}),this.installExtension(this.selectedRelease)):Quasar.Notify.create({type:"warning",message:"Invoice tracking lost!"})})},unsubscribeFromPaylinkWs(){try{this.paylinkWebsocket&&this.paylinkWebsocket.close()}catch(e){console.warn(e)}},hasNewVersion(e){if(e.installedRelease&&e.latestRelease)return e.installedRelease.version!==e.latestRelease.version},isInstalledVersion(e,t){if(e.installedRelease)return e.installedRelease.source_repo===t.source_repo&&e.installedRelease.version===t.version},getReleaseIcon:e=>e.is_version_compatible?e.isInstalled?"download_done":"download":"block",getReleaseIconColor:e=>e.is_version_compatible?e.isInstalled?"text-green":"":"text-red",extensionOpenUrl:e=>e.isWasm?`/ext/${e.id}`:`/${e.id}`,permissionLabelById(e){const t=`extension_permission_${String(e).replace(/[^A-Za-z0-9]/g,"_")}`,s=this.$t(t);return s===t?e:s},walletName(e){const t=(this.g.user.wallets||[]).find(t=>t.id===e);return t?t.name||t.id:e},userPermissionRowCaption:e=>`${e.walletName} (${e.walletId.slice(0,8)}...)`,isBackgroundPaymentPermission:e=>"wallet.pay_invoice_background"===e.permissionId,userPermissionGrantPayload(e){return{wallet_id:e.walletId,max_amount:this.positiveInteger(e.grant.max_amount,0),destination_policy:this.backgroundPaymentDestinationPolicy(e.grant.destination_policy)}},positiveInteger(e,t){const s=Number(e);return!Number.isFinite(s)||s<=0?t:Math.floor(s)},backgroundPaymentDestinationPolicy:e=>"external_allowed"===e?"external_allowed":"own_wallets_only",backgroundPaymentGrantIncreased(e,t){const s=e.grant._original||{},a=this.positiveInteger(s.max_amount,0),i=this.backgroundPaymentDestinationPolicy(s.destination_policy);return t.max_amount>a||"own_wallets_only"===i&&"external_allowed"===t.destination_policy},confirmUserPermissionIncrease:()=>new Promise(e=>{let t=!1;const s=s=>{t||(t=!0,e(s))};LNbits.utils.confirmDialog("This increases what the extension can do with this wallet. Continue?").onOk(()=>s(!0)).onCancel(()=>s(!1)).onDismiss(()=>s(!1))}),async saveUserPermissionGrant(e){if(!this.isBackgroundPaymentPermission(e))return;const t=this.userPermissionGrantPayload(e);if(t.max_amount){if(!this.backgroundPaymentGrantIncreased(e,t)||await this.confirmUserPermissionIncrease()){this.managedExtensionPermissions.savingKey=e.key;try{await LNbits.api.request("POST",`/api/v1/extension/${this.selectedExtension.id}/permissions/background-payment`,null,t),Quasar.Notify.create({type:"positive",message:"Permission updated."}),await this.loadManagedExtensionPermissions()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}finally{this.managedExtensionPermissions.savingKey=""}}}else Quasar.Notify.create({type:"negative",message:"Max payment amount must be greater than zero."})},deleteUserPermissionGrant(e){LNbits.utils.confirmDialog("Remove this permission grant?").onOk(async()=>{this.managedExtensionPermissions.deletingKey=e.key;try{const t=encodeURIComponent(e.grantId);await LNbits.api.request("DELETE",`/api/v1/extension/${this.selectedExtension.id}/permissions/user/${t}`),Quasar.Notify.create({type:"positive",message:"Permission removed."}),await this.loadManagedExtensionPermissions()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}finally{this.managedExtensionPermissions.deletingKey=""}})},async getGitHubReleaseDetails(e){if(!e.is_github_release||e.loaded)return;const[t,s]=e.source_repo.split("/");e.inProgress=!0;try{const{data:a}=await LNbits.api.request("GET",`/api/v1/extension/release/${t}/${s}/${e.version}`);e.loaded=!0,e.is_version_compatible=a.is_version_compatible,e.min_lnbits_version=a.min_lnbits_version,e.warning=a.warning,e.extension_type=a.extension_type,e.permissions=a.permissions||[]}catch(t){console.warn(t),e.error=t,LNbits.utils.notifyApiError(t)}finally{e.inProgress=!1}},async resolveExtensionPermissionGrant(e){const t=this.extensionPermissionsForRelease(e);if(!this.releaseRequiresPermissionGrant(e)||!t.length)return[];if(e.grantedPermissions)return e.grantedPermissions;const s=await this.confirmExtensionPermissions(t);return s?(e.grantedPermissions=s,s):null},extensionPermissionsForRelease(e){return e.permissions||this.selectedExtension?.permissions||[]},releaseRequiresPermissionGrant(e){return"wasm"===e.extension_type||!0===this.selectedExtension?.isWasm},confirmExtensionPermissions(e){return new Promise(t=>{this.selectedRelease=null,this.permissionGrant={show:!0,permissions:this.cloneEditableExtensionPermissions(e),resolve:t},this.showManageExtensionDialog=!0})},grantExtensionPermissions(){this.validateExtensionPermissionLimits(this.permissionGrant.permissions)&&this.resolveExtensionPermissionDialog(this.cloneEditableExtensionPermissions(this.permissionGrant.permissions))},cancelExtensionPermissions(){this.resolveExtensionPermissionDialog(null)},onManageExtensionDialogHide(){this.permissionGrant.show&&this.resolveExtensionPermissionDialog(null)},resolveExtensionPermissionDialog(e){const t=this.permissionGrant.resolve;this.permissionGrant={show:!1,permissions:[],resolve:null},this.showManageExtensionDialog=!1,t&&t(e)},permissionGrantHasHighRisk(){return window.LNbitsExtensionPermissions.hasHighRisk({permissions:this.permissionGrant.permissions,extensions:this.extensions,translate:e=>this.$t(e)})},async selectAllUpdatableExtensionss(){this.updatableExtensions.forEach(e=>e.selectedForUpdate=!0)},async updateSelectedExtensions(){let e=0;for(const t of this.updatableExtensions)try{if(!t.selectedForUpdate)continue;if(t.isWasm){Quasar.Notify.create({type:"warning",message:`Skipping ${t.id}; this extension update requires permission approval.`});continue}t.inProgress=!0,await LNbits.api.request("POST","/api/v1/extension",null,{ext_id:t.id,archive:t.latestRelease.archive,source_repo:t.latestRelease.source_repo,payment_hash:t.latestRelease.payment_hash,version:t.latestRelease.version}),e++,t.isAvailable=!0,t.isInstalled=!0,t.isUpgraded=!0,t.inProgress=!1,t.installedRelease=t.latestRelease,t.isActive=!0,this.toggleExtension(t)}catch(e){console.warn(e),Quasar.Notify.create({type:"negative",message:`Failed to update ${t.id}!`})}finally{t.inProgress=!1}Quasar.Notify.create({type:e?"positive":"warning",message:`${e||"No"} extensions updated!`}),this.showUpdateAllDialog=!1},formatAvg(e){const t=Number(e||0);return Math.round(t/2/100*2)/2},async loadReviewStats(){if(this.reviewsUrl)try{const{data:e}=await LNbits.api.request("GET","/api/v1/extension/reviews/tags"),t={};e.forEach(e=>{t[e.tag]=e}),this.extensions.forEach(e=>{e.reviewStats=t[e.id]||null}),this.filterExtensions(this.searchTerm,this.tab)}catch(e){console.warn(e)}else console.info("Extension reviews are not configured")},async openReviews(e){const t=e||(this.selectedExtensionDetails?this.extensions.find(e=>e.id===this.selectedExtensionDetails.id):null);t&&(this.reviewsUrl?(this.reviewsDialog.extension=t,this.selectedExtension=e,this.reviewsDialog.show=!0,await this.getTagReviews()):Quasar.Notify.create({type:"warning",message:this.$t("reviews_url_not_configured")}))},async getTagReviews(e){if(this.reviewsUrl)try{this.reviewsTable.loading=!0;const t=LNbits.utils.prepareFilterQuery(this.reviewsTable,e),{data:s}=await LNbits.api.request("GET",`/api/v1/extension/reviews/${this.selectedExtension.id}?${t}`);this.reviews=s.data,this.reviewsTable.pagination.rowsNumber=s.total}catch(e){LNbits.utils.notifyApiError(e)}finally{this.reviewsTable.loading=!1}else Quasar.Notify.create({type:"warning",message:this.$t("reviews_url_not_configured")})},formatReviewDate(e){if(!e)return"";const t=Number(e);return Number.isNaN(t)?this.utils.formatDate(e):this.utils.formatTimestamp(t)},async submitReview(){if(this.reviewsDialog.extension&&this.reviewsUrl){this.reviewsDialog.submitting=!0;try{const e={tag:this.reviewsDialog.extension.id,name:this.reviewsDialog.form.name,rating:100*this.reviewsDialog.form.rating,comment:this.reviewsDialog.form.comment},{data:t}=await LNbits.api.request("PUT","/api/v1/extension/reviews",null,e);t.payment_request?this.openInvoiceDialog(t.payment_request,t.payment_hash):(Quasar.Notify.create({type:"positive",message:"Review submitted"}),this.resetReviewForm(),await this.getTagReviews(),await this.loadReviewStats())}catch(e){LNbits.utils.notifyApiError(e)}finally{this.reviewsDialog.submitting=!1}}},openInvoiceDialog(e,t){this.paymentDialog.invoice=e,this.paymentDialog.hash=t,this.paymentDialog.show=!0,this.listenForPayment(t)},resetReviewForm(){this.reviewsDialog.form={name:"",rating:0,comment:""},this.paymentDialog={show:!1,invoice:"",hash:""}},listenForPayment(e){try{const t=new URL(this.reviewsUrl);t.protocol="https:"===t.protocol?"wss:":"ws:",t.pathname=`/api/v1/ws/${e}`;const s=new WebSocket(t);s.addEventListener("message",async()=>{Quasar.Notify.create({type:"positive",message:this.$t("reviews_invoice_paid")}),this.paymentDialog.show=!1,this.resetReviewForm(),setTimeout(async()=>{await this.getTagReviews()},1e3),await this.loadReviewStats(),s.close()})}catch(e){console.warn(e)}},async fetchAllExtensions(){try{const{data:e}=await LNbits.api.request("GET","/api/v1/extension/all");return e.forEach(e=>{e.categories?.forEach(e=>this.categories.add(e))}),e}catch(e){return console.warn(e),LNbits.utils.notifyApiError(e),[]}}},async created(){this.extensions=await this.fetchAllExtensions(),this.extbuilderEnabled=this.g.user.admin||this.g.settings.extBuilder,this.reviewsUrl=this.g.settings.extensionsReviewsUrl,0===this.g.user.extensions.length&&(this.tab="all");const e=window.location.hash.replace("#",""),t=this.extensions.find(t=>t.id===e);t&&(this.searchTerm=t.id,t.isInstalled&&(this.tab="installed")),this.updatableExtensions=this.extensions.filter(e=>this.hasNewVersion(e)),await this.loadReviewStats(),this.filterExtensions(this.searchTerm,this.tab)}},window.PageFirstInstall={template:"#page-first-install",data:()=>({loginData:{isPwd:!0,isPwdRepeat:!0,username:"",password:"",passwordRepeat:"",firstInstallToken:""}}),computed:{checkPasswordsMatch(){return this.loginData.password!==this.loginData.passwordRepeat}},methods:{setPassword(){LNbits.api.request("PUT","/api/v1/auth/first_install",null,{username:this.loginData.username,password:this.loginData.password,password_repeat:this.loginData.passwordRepeat,first_install_token:this.loginData.firstInstallToken}).then(async()=>{const e=await LNbits.api.getAuthUser();this.g.user=LNbits.map.user(e.data),this.g.isPublicPage=!1,this.$router.push("/admin")}).catch(this.utils.notifyApiError)}},created(){const e=new URLSearchParams(window.location.search);this.loginData.firstInstallToken=e.get("token")||""}},window.PagePayments={template:"#page-payments",data:()=>({payments:[],dailyChartData:[],searchDate:{from:null,to:null},searchData:{wallet_id:null,payment_hash:null,memo:null,internal_memo:null},statusFilters:{success:!0,pending:!0,failed:!0,incoming:!0,outgoing:!0},chartData:{showPaymentStatus:!0,showPaymentTags:!0,showBalance:!0,showWalletsSize:!1,showBalanceInOut:!1,showPaymentCountInOut:!1},searchOptions:{status:[]},paymentsTable:{columns:[{name:"status",align:"left",label:"Status",field:"status",sortable:!1},{name:"created_at",align:"left",label:"Created At",field:"created_at",sortable:!0},{name:"amount",align:"right",label:"Amount",field:"amount",sortable:!0},{name:"amountFiat",align:"right",label:"Fiat",field:"amountFiat",sortable:!1},{name:"fee_sats",align:"left",label:"Fee",field:"fee_sats",sortable:!0},{name:"tag",align:"left",label:"Tag",field:"tag",sortable:!1},{name:"memo",align:"left",label:"Memo",field:"memo",sortable:!1,max_length:20},{name:"internal_memo",align:"left",label:"Internal Memo",field:"internal_memo",sortable:!1,max_length:20},{name:"wallet_id",align:"left",label:"Wallet (ID)",field:"wallet_id",sortable:!1},{name:"payment_hash",align:"left",label:"Payment Hash",field:"payment_hash",sortable:!1}],pagination:{sortBy:"created_at",rowsPerPage:25,page:1,descending:!0,rowsNumber:10},search:null,hideEmpty:!0,loading:!1},chartsReady:!1,showDetails:!1,paymentDetails:null,lnbitsBalance:0}),async mounted(){this.chartsReady=!0,await this.$nextTick(),this.initCharts(),await this.fetchPayments()},computed:{},methods:{async fetchPayments(e){const t=Object.entries(this.searchData).reduce((e,[t,s])=>s?(e[t]=s,e):e,{});delete t["time[ge]"],delete t["time[le]"],this.searchDate.from&&(t["time[ge]"]=this.searchDate.from+"T00:00:00"),this.searchDate.to&&(t["time[le]"]=this.searchDate.to+"T23:59:59"),this.paymentsTable.filter=t;try{const t=LNbits.utils.prepareFilterQuery(this.paymentsTable,e),{data:s}=await LNbits.api.request("GET",`/api/v1/payments/all/paginated?${t}`);this.paymentsTable.pagination.rowsNumber=s.total,this.payments=s.data.map(e=>(e.extra&&e.extra.tag&&(e.tag=e.extra.tag),e.timeFrom=moment.utc(e.created_at).local().fromNow(),e.outgoing=e.amount<0,e.amount=new Intl.NumberFormat(this.g.locale).format(e.amount/1e3)+" sats",e.extra?.wallet_fiat_amount&&(e.amountFiat=this.formatCurrency(e.extra.wallet_fiat_amount,e.extra.wallet_fiat_currency)),e.extra?.internal_memo&&(e.internal_memo=e.extra.internal_memo),e.fee_sats=new Intl.NumberFormat(this.g.locale).format(e.fee/1e3)+" sats",e))}catch(e){console.error(e),LNbits.utils.notifyApiError(e)}finally{this.updateCharts(e)}},async searchPaymentsBy(e,t){e&&(this.searchData[e]=t),await this.fetchPayments()},clearDateSeach(){this.searchDate={from:null,to:null},delete this.paymentsTable.filter["time[ge]"],delete this.paymentsTable.filter["time[le]"],this.fetchPayments()},searchByDate(){"string"==typeof this.searchDate&&(this.searchDate={from:this.searchDate,to:this.searchDate}),this.searchDate.from&&(this.paymentsTable.filter["time[ge]"]=this.searchDate.from+"T00:00:00"),this.searchDate.to&&(this.paymentsTable.filter["time[le]"]=this.searchDate.to+"T23:59:59"),this.fetchPayments()},handleFilterChanged(){const{success:e,pending:t,failed:s,incoming:a,outgoing:i}=this.statusFilters;delete this.searchData["status[ne]"],delete this.searchData["status[eq]"],e&&t&&s||(e&&t?this.searchData["status[ne]"]="failed":e&&s?this.searchData["status[ne]"]="pending":s&&t?this.searchData["status[ne]"]="success":e?this.searchData["status[eq]"]="success":t?this.searchData["status[eq]"]="pending":s&&(this.searchData["status[eq]"]="failed")),delete this.searchData["amount[ge]"],delete this.searchData["amount[le]"],a&&i||(a?this.searchData["amount[ge]"]="0":i&&(this.searchData["amount[le]"]="0")),this.fetchPayments()},showDetailsToggle(e){return this.paymentDetails=e,this.showDetails=!this.showDetails},formatCurrency(e,t){try{return LNbits.utils.formatCurrency(e,t)}catch(t){return console.error(t),`${e} ???`}},shortify:(e,t=10)=>(valueLength=(e||"").length,valueLength<=t?e:`${e.substring(0,5)}...${e.substring(valueLength-5,valueLength)}`),async updateCharts(e){let t=LNbits.utils.prepareFilterQuery(this.paymentsTable,e);try{const{data:e}=await LNbits.api.request("GET",`/api/v1/payments/stats/count?${t}&count_by=status`);e.sort((e,t)=>e.field-t.field).reverse(),this.searchOptions.status=e.map(e=>e.field),this.paymentsStatusChart.data.datasets[0].data=e.map(e=>e.total),this.paymentsStatusChart.data.labels=[...this.searchOptions.status],this.paymentsStatusChart.update()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}try{const{data:e}=await LNbits.api.request("GET",`/api/v1/payments/stats/wallets?${t}`),s=e.map(e=>e.balance/e.payments_count),a=Math.min(...s),i=Math.max(...s),n=e=>Math.floor(3+22*(e-a)/(i-a)),o=this.randomColors(20),r=e.map((e,t)=>({data:[{x:e.payments_count,y:e.balance,r:n(Math.max(e.balance/e.payments_count,5))}],label:e.wallet_name,wallet_id:e.wallet_id,backgroundColor:o[t%100],hoverOffset:4}));this.paymentsWalletsChart.data.datasets=r,this.paymentsWalletsChart.update()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}try{const{data:e}=await LNbits.api.request("GET",`/api/v1/payments/stats/count?${t}&count_by=tag`);this.searchOptions.tag=e.map(e=>e.field),this.searchOptions.status.sort(),this.paymentsTagsChart.data.datasets[0].data=e.map(e=>e.total),this.paymentsTagsChart.data.labels=e.map(e=>e.field||"core"),this.paymentsTagsChart.update()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}try{const t=Object.entries(this.searchData).reduce((e,[t,s])=>s?(e[t]=s,e):e,{}),s={...this.paymentsTable,filter:t},a=LNbits.utils.prepareFilterQuery(s,e);let{data:i}=await LNbits.api.request("GET",`/api/v1/payments/stats/daily?${a}`);const n=this.searchDate.from+"T00:00:00",o=this.searchDate.to+"T23:59:59";this.lnbitsBalance=i.length?i[i.length-1].balance:0,i=i.filter(e=>this.searchDate.from&&this.searchDate.to?e.date>=n&&e.date<=o:this.searchDate.from?e.date>=n:!this.searchDate.to||e.date<=o),this.paymentsDailyChart.data.datasets=[{label:"Balance",data:i.map(e=>e.balance),pointStyle:!1,borderWidth:2,tension:.7,fill:1},{label:"Fees",data:i.map(e=>e.fee),pointStyle:!1,borderWidth:1,tension:.4,fill:1}],this.paymentsDailyChart.data.labels=i.map(e=>e.date.substring(0,10)),this.paymentsDailyChart.update(),this.paymentsBalanceInOutChart.data.datasets=[{label:"Incoming Payments Balance",data:i.map(e=>e.balance_in)},{label:"Outgoing Payments Balance",data:i.map(e=>e.balance_out)}],this.paymentsBalanceInOutChart.data.labels=i.map(e=>e.date.substring(0,10)),this.paymentsBalanceInOutChart.update(),this.paymentsCountInOutChart.data.datasets=[{label:"Incoming Payments Count",data:i.map(e=>e.count_in)},{label:"Outgoing Payments Count",data:i.map(e=>-e.count_out)}],this.paymentsCountInOutChart.data.labels=i.map(e=>e.date.substring(0,10)),this.paymentsCountInOutChart.update()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}},async initCharts(){const e=this.$q.localStorage.getItem("lnbits.payments.chartData")||{};this.chartData={...this.chartData,...e},this.chartsReady?(this.paymentsStatusChart=new Chart(this.$refs.paymentsStatusChart.getContext("2d"),{type:"doughnut",options:{responsive:!0,maintainAspectRatio:!1,plugins:{title:{display:!1}},onClick:(e,t,s)=>{if(t[0]){const e=t[0].index;this.searchPaymentsBy("status",s.data.labels[e])}}},data:{datasets:[{label:"",data:[],backgroundColor:["rgb(0, 205, 86)","rgb(64, 72, 78)","rgb(255, 99, 132)"],hoverOffset:4}]}}),this.paymentsWalletsChart=new Chart(this.$refs.paymentsWalletsChart.getContext("2d"),{type:"bubble",options:{responsive:!0,maintainAspectRatio:!1,plugins:{legend:{display:!1},title:{display:!1}},onClick:(e,t,s)=>{if(t[0]){const e=t[0].datasetIndex;this.searchPaymentsBy("wallet_id",s.data.datasets[e].wallet_id)}}},data:{datasets:[{label:"",data:[],backgroundColor:this.randomColors(20),hoverOffset:4}]}}),this.paymentsTagsChart=new Chart(this.$refs.paymentsTagsChart.getContext("2d"),{type:"pie",options:{responsive:!0,maintainAspectRatio:!1,plugins:{title:{display:!1},legend:{display:!1,title:{display:!1,text:"Tags"}}},onClick:(e,t,s)=>{if(t[0]){const e=t[0].index;this.searchPaymentsBy("tag",s.data.labels[e])}}},data:{datasets:[{label:"",data:[],backgroundColor:this.randomColors(10),hoverOffset:4}]}}),this.paymentsDailyChart=new Chart(this.$refs.paymentsDailyChart.getContext("2d"),{type:"line",options:{responsive:!0,maintainAspectRatio:!1,plugins:{title:{display:!1},legend:{display:!0,title:{display:!1,text:"Tags"}}}},data:{datasets:[{label:"",data:[],backgroundColor:this.randomColors(10),hoverOffset:4}]}}),this.paymentsBalanceInOutChart=new Chart(this.$refs.paymentsBalanceInOutChart.getContext("2d"),{type:"bar",options:{responsive:!0,maintainAspectRatio:!1,plugins:{title:{display:!1},legend:{display:!0,title:{display:!1,text:"Tags"}}},scales:{x:{stacked:!0},y:{stacked:!0}}},data:{datasets:[{label:"",data:[],backgroundColor:this.randomColors(50),hoverOffset:4}]}}),this.paymentsCountInOutChart=new Chart(this.$refs.paymentsCountInOutChart.getContext("2d"),{type:"bar",options:{responsive:!0,maintainAspectRatio:!1,plugins:{title:{display:!1},legend:{display:!0,title:{display:!1,text:""}}},scales:{x:{stacked:!0},y:{stacked:!0}}},data:{datasets:[{label:"",data:[],backgroundColor:this.randomColors(80),hoverOffset:4}]}})):console.warn("Charts are not ready yet. Initialization delayed.")},saveChartsPreferences(){this.$q.localStorage.set("lnbits.payments.chartData",this.chartData)},randomColors(e=1){const t=[];for(let s=1;s<=10;s++)for(let a=1;a<=10;a++)t.push(`rgb(${a*e*33%200}, ${71*(s+a+e)%255}, ${(s+30*e)%255})`);return t}}},window.PageNode={template:"#page-node",config:{globalProperties:{LNbits:LNbits,msg:"hello"}},data(){return{isSuperUser:!1,wallet:{},tab:"dashboard",payments:1e3,info:{},channel_stats:{},channels:{data:[],filter:""},activeBalance:{},ranks:{},peers:{data:[],filter:""},connectPeerDialog:{show:!1,data:{}},setFeeDialog:{show:!1,data:{fee_ppm:0,fee_base_msat:0}},openChannelDialog:{show:!1,data:{}},closeChannelDialog:{show:!1,data:{}},nodeInfoDialog:{show:!1,data:{}},transactionDetailsDialog:{show:!1,data:{}},states:[{label:"Active",value:"active",color:"green"},{label:"Pending",value:"pending",color:"orange"},{label:"Inactive",value:"inactive",color:"grey"},{label:"Closed",value:"closed",color:"red"}],stateFilters:[{label:"Active",value:"active"},{label:"Pending",value:"pending"}],paymentsTable:{data:[],columns:[{name:"pending",label:""},{name:"date",align:"left",label:this.$t("date"),field:"date",sortable:!0},{name:"sat",align:"right",label:this.$t("amount"),field:e=>this.formatMsat(e.amount),sortable:!0},{name:"fee",align:"right",label:this.$t("fee"),field:"fee"},{name:"destination",align:"right",label:"Destination",field:"destination"},{name:"memo",align:"left",label:this.$t("memo"),field:"memo"}],pagination:{rowsPerPage:10,page:1,rowsNumber:10},filter:null},invoiceTable:{data:[],columns:[{name:"pending",label:""},{name:"paid_at",field:"paid_at",align:"left",label:"Paid at",sortable:!0},{name:"expiry",label:this.$t("expiry"),field:"expiry",align:"left",sortable:!0},{name:"amount",label:this.$t("amount"),field:e=>this.formatMsat(e.amount),sortable:!0},{name:"memo",align:"left",label:this.$t("memo"),field:"memo"}],pagination:{rowsPerPage:10,page:1,rowsNumber:10},filter:null}}},created(){this.getInfo(),this.get1MLStats()},watch:{tab(e){"transactions"!==e||this.paymentsTable.data.length?"channels"!==e||this.channels.data.length||(this.getChannels(),this.getPeers()):(this.getPayments(),this.getInvoices())}},computed:{checkChanges(){return!_.isEqual(this.settings,this.formData)},filteredChannels(){return this.stateFilters?this.channels.data.filter(e=>this.stateFilters.find(({value:t})=>t==e.state)):this.channels.data},totalBalance(){return this.filteredChannels.reduce((e,t)=>(e.local_msat+=t.balance.local_msat,e.remote_msat+=t.balance.remote_msat,e.total_msat+=t.balance.total_msat,e),{local_msat:0,remote_msat:0,total_msat:0})}},methods:{formatMsat:e=>LNbits.utils.formatMsat(e),nodeApi(e,t,s){const a=new URLSearchParams(s?.query);return LNbits.api.request(e,`/node/api/v1${t}?${a}`,{},s?.data).catch(e=>{LNbits.utils.notifyApiError(e)})},getChannel(e){return this.nodeApi("GET",`/channels/${e}`).then(e=>{this.setFeeDialog.data.fee_ppm=e.data.fee_ppm,this.setFeeDialog.data.fee_base_msat=e.data.fee_base_msat})},getChannels(){return this.nodeApi("GET","/channels").then(e=>{this.channels.data=e.data})},getInfo(){return this.nodeApi("GET","/info").then(e=>{this.info=e.data,this.channel_stats=e.data.channel_stats}).catch(()=>{this.info={},this.channel_stats={}})},get1MLStats(){return this.nodeApi("GET","/rank").then(e=>{this.ranks=e.data}).catch(()=>{this.ranks={}})},getPayments(e){e&&(this.paymentsTable.pagination=e.pagination);let t=this.paymentsTable.pagination;const s={limit:t.rowsPerPage,offset:(t.page-1)*t.rowsPerPage??0};return this.nodeApi("GET","/payments",{query:s}).then(e=>{this.paymentsTable.data=e.data.data,this.paymentsTable.pagination.rowsNumber=e.data.total})},getInvoices(e){e&&(this.invoiceTable.pagination=e.pagination);let t=this.invoiceTable.pagination;const s={limit:t.rowsPerPage,offset:(t.page-1)*t.rowsPerPage??0};return this.nodeApi("GET","/invoices",{query:s}).then(e=>{this.invoiceTable.data=e.data.data,this.invoiceTable.pagination.rowsNumber=e.data.total})},getPeers(){return this.nodeApi("GET","/peers").then(e=>{this.peers.data=e.data})},connectPeer(){this.nodeApi("POST","/peers",{data:this.connectPeerDialog.data}).then(()=>{this.connectPeerDialog.show=!1,this.getPeers()})},disconnectPeer(e){LNbits.utils.confirmDialog("Do you really wanna disconnect this peer?").onOk(()=>{this.nodeApi("DELETE",`/peers/${e}`).then(e=>{Quasar.Notify.create({message:"Disconnected",icon:null}),this.needsRestart=!0,this.getPeers()})})},setChannelFee(e){this.nodeApi("PUT",`/channels/${e}`,{data:this.setFeeDialog.data}).then(e=>{this.setFeeDialog.show=!1,this.getChannels()}).catch(LNbits.utils.notifyApiError)},openChannel(){this.nodeApi("POST","/channels",{data:this.openChannelDialog.data}).then(e=>{this.openChannelDialog.show=!1,this.getChannels()}).catch(e=>{console.log(e)})},showCloseChannelDialog(e){this.closeChannelDialog.show=!0,this.closeChannelDialog.data={force:!1,short_id:e.short_id,...e.point}},closeChannel(){this.nodeApi("DELETE","/channels",{query:this.closeChannelDialog.data}).then(e=>{this.closeChannelDialog.show=!1,this.getChannels()})},showSetFeeDialog(e){this.setFeeDialog.show=!0,this.setFeeDialog.channel_id=e,this.getChannel(e)},showOpenChannelDialog(e){this.openChannelDialog.show=!0,this.openChannelDialog.data={peer_id:e,funding_amount:0}},showNodeInfoDialog(e){this.nodeInfoDialog.show=!0,this.nodeInfoDialog.data=e},showTransactionDetailsDialog(e){this.transactionDetailsDialog.show=!0,this.transactionDetailsDialog.data=e},shortenNodeId:e=>e?e.substring(0,5)+"..."+e.substring(e.length-5):"..."}},window.PageNodePublic={template:"#page-node-public",data:()=>({enabled:!1,isSuperUser:!1,wallet:{},tab:"dashboard",payments:1e3,info:{},channel_stats:{},channels:[],activeBalance:{},ranks:{},peers:[],connectPeerDialog:{show:!1,data:{}},openChannelDialog:{show:!1,data:{}},closeChannelDialog:{show:!1,data:{}},nodeInfoDialog:{show:!1,data:{}},states:[{label:"Active",value:"active",color:"green"},{label:"Pending",value:"pending",color:"orange"},{label:"Inactive",value:"inactive",color:"grey"},{label:"Closed",value:"closed",color:"red"}]}),created(){this.getInfo(),this.get1MLStats()},methods:{formatMsat:e=>LNbits.utils.formatMsat(e),api:(e,t,s)=>LNbits.api.request(e,"/node/public/api/v1"+t,{},s),getInfo(){this.api("GET","/info",{}).then(e=>{this.info=e.data,this.channel_stats=e.data.channel_stats,this.enabled=!0}).catch(()=>{this.info={},this.channel_stats={}})},get1MLStats(){this.api("GET","/rank",{}).then(e=>{this.ranks=e.data}).catch(()=>{this.ranks={}})}}},window.PageAudit={template:"#page-audit",data:()=>({chartsReady:!1,auditEntries:[],searchData:{user_id:"",ip_address:"",request_type:"",component:"",request_method:"",response_code:"",path:""},searchOptions:{component:[],request_method:[],response_code:[]},auditTable:{columns:[{name:"created_at",align:"center",label:"Date",field:"created_at",sortable:!0},{name:"duration",align:"left",label:"Duration (sec)",field:"duration",sortable:!0},{name:"component",align:"left",label:"Component",field:"component",sortable:!1},{name:"request_method",align:"left",label:"Method",field:"request_method",sortable:!1},{name:"response_code",align:"left",label:"Code",field:"response_code",sortable:!1},{name:"user_id",align:"left",label:"User Id",field:"user_id",sortable:!1},{name:"ip_address",align:"left",label:"IP Address",field:"ip_address",sortable:!1},{name:"path",align:"left",label:"Path",field:"path",sortable:!1}],pagination:{sortBy:"created_at",rowsPerPage:10,page:1,descending:!0,rowsNumber:10},search:null,hideEmpty:!0,loading:!1},auditDetailsDialog:{data:null,show:!1}}),async created(){},async mounted(){this.chartsReady=!0,await this.$nextTick(),this.initCharts(),await this.fetchAudit()},methods:{async fetchAudit(e){try{const t=LNbits.utils.prepareFilterQuery(this.auditTable,e),{data:s}=await LNbits.api.request("GET",`/audit/api/v1?${t}`);this.auditTable.pagination.rowsNumber=s.total,this.auditEntries=s.data,await this.fetchAuditStats(e)}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}finally{this.auditTable.loading=!1}},async fetchAuditStats(e){try{const t=LNbits.utils.prepareFilterQuery(this.auditTable,e),{data:s}=await LNbits.api.request("GET",`/audit/api/v1/stats?${t}`),a=s.request_method.map(e=>e.field);this.searchOptions.request_method=[...new Set(this.searchOptions.request_method.concat(a))],this.requestMethodChart.data.labels=a,this.requestMethodChart.data.datasets[0].data=s.request_method.map(e=>e.total),this.requestMethodChart.update();const i=s.response_code.map(e=>e.field);this.searchOptions.response_code=[...new Set(this.searchOptions.response_code.concat(i))],this.responseCodeChart.data.labels=i,this.responseCodeChart.data.datasets[0].data=s.response_code.map(e=>e.total),this.responseCodeChart.update();const n=s.component.map(e=>e.field);this.searchOptions.component=[...new Set(this.searchOptions.component.concat(n))],this.componentUseChart.data.labels=n,this.componentUseChart.data.datasets[0].data=s.component.map(e=>e.total),this.componentUseChart.update(),this.longDurationChart.data.labels=s.long_duration.map(e=>e.field),this.longDurationChart.data.datasets[0].data=s.long_duration.map(e=>e.total),this.longDurationChart.update()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}},async searchAuditBy(e,t){e&&(this.searchData[e]=t),this.auditTable.filter=Object.entries(this.searchData).reduce((e,[t,s])=>s?(e[t]=s,e):e,{}),await this.fetchAudit()},showDetailsDialog(e){const t=JSON.parse(e?.request_details||"");try{t.body&&(t.body=JSON.parse(t.body))}catch(e){}this.auditDetailsDialog.data=JSON.stringify(t,null,4),this.auditDetailsDialog.show=!0},shortify:e=>(valueLength=(e||"").length,valueLength<=10?e:`${e.substring(0,5)}...${e.substring(valueLength-5,valueLength)}`),async initCharts(){this.chartsReady?(this.responseCodeChart=new Chart(this.$refs.responseCodeChart.getContext("2d"),{type:"doughnut",options:{responsive:!0,plugins:{legend:{position:"bottom"},title:{display:!1,text:"HTTP Response Codes"}},onClick:(e,t,s)=>{if(t[0]){const e=t[0].index;this.searchAuditBy("response_code",s.data.labels[e])}}},data:{datasets:[{label:"",data:[20,10],backgroundColor:["rgb(100, 99, 200)","rgb(54, 162, 235)","rgb(255, 205, 86)","rgb(255, 5, 86)","rgb(25, 205, 86)","rgb(255, 205, 250)"]}],labels:[]}}),this.requestMethodChart=new Chart(this.$refs.requestMethodChart.getContext("2d"),{type:"bar",options:{responsive:!0,maintainAspectRatio:!1,plugins:{title:{display:!1}},onClick:(e,t,s)=>{if(t[0]){const e=t[0].index;this.searchAuditBy("request_method",s.data.labels[e])}}},data:{datasets:[{label:"",data:[],backgroundColor:["rgb(255, 99, 132)","rgb(54, 162, 235)","rgb(255, 205, 86)","rgb(255, 5, 86)","rgb(25, 205, 86)","rgb(255, 205, 250)"],hoverOffset:4}]}}),this.componentUseChart=new Chart(this.$refs.componentUseChart.getContext("2d"),{type:"pie",options:{responsive:!0,plugins:{legend:{position:"xxx"},title:{display:!1,text:"Components"}},onClick:(e,t,s)=>{if(t[0]){const e=t[0].index;this.searchAuditBy("component",s.data.labels[e])}}},data:{datasets:[{data:[],backgroundColor:["rgb(255, 99, 132)","rgb(54, 162, 235)","rgb(255, 205, 86)","rgb(255, 5, 86)","rgb(25, 205, 86)","rgb(255, 205, 250)","rgb(100, 205, 250)","rgb(120, 205, 250)","rgb(140, 205, 250)","rgb(160, 205, 250)"],hoverOffset:4}]}}),this.longDurationChart=new Chart(this.$refs.longDurationChart.getContext("2d"),{type:"bar",options:{responsive:!0,indexAxis:"y",maintainAspectRatio:!1,plugins:{legend:{title:{display:!1,text:"Long Duration"}}},onClick:(e,t,s)=>{if(t[0]){const e=t[0].index;this.searchAuditBy("path",s.data.labels[e])}}},data:{datasets:[{label:"",data:[],backgroundColor:["rgb(255, 99, 132)","rgb(54, 162, 235)","rgb(255, 205, 86)","rgb(255, 5, 86)","rgb(25, 205, 86)","rgb(255, 205, 250)","rgb(100, 205, 250)","rgb(120, 205, 250)","rgb(140, 205, 250)","rgb(160, 205, 250)"],hoverOffset:4}]}})):console.warn("Charts are not ready yet. Initialization delayed.")}}},window.PageWallet={template:"#page-wallet",data:()=>({parse:{show:!1,invoice:null,lnurlpay:null,lnurlauth:null,sending:!1,data:{request:"",amount:0,comment:"",internalMemo:null,unit:"sat"},paymentChecker:null,copy:{show:!1},camera:{show:!1,camera:"auto"}},receive:{show:!1,status:"pending",paymentReq:null,paymentHash:null,amountMsat:null,minMax:[0,21e14],lnurl:null,units:[],unit:"sat",fiatProvider:"",data:{amount:null,memo:"",internalMemo:null,payment_hash:null}},update:{name:null,currency:null},hasNfc:!1,nfcReaderAbortController:null,formattedFiatAmount:0,paymentFilter:{"status[ne]":"failed"},chartConfig:Quasar.LocalStorage.getItem("lnbits.wallets.chartConfig")||{showPaymentInOutChart:!0,showBalanceChart:!0,showBalanceInOutChart:!0}}),computed:{canPay(){return!!this.parse.invoice&&(this.parse.invoice.expired?(Quasar.Notify.create({message:"Invoice has expired",color:"negative"}),!1):this.parse.invoice.sat<=this.g.wallet.sat)},formattedAmount(){return"sat"==this.receive.unit&&this.g.isSatsDenomination?LNbits.utils.formatMsat(this.receive.amountMsat)+" sat":LNbits.utils.formatCurrency(Number(this.receive.data.amount).toFixed(2),this.g.isSatsDenomination?this.receive.unit:this.g.denomination)},formattedSatAmount(){return LNbits.utils.formatMsat(this.receive.amountMsat)+" sat"}},methods:{handleSendLnurl(e){this.parse.data.request=e,this.parse.show=!0,this.lnurlScan()},msatoshiFormat:e=>LNbits.utils.formatSat(e/1e3),showReceiveDialog(){this.receive.show=!0,this.receive.status="pending",this.receive.paymentReq=null,this.receive.paymentHash=null,this.receive.data.amount=null,this.receive.data.memo=null,this.receive.data.internalMemo=null,this.receive.data.payment_hash=null,this.receive.units=["sat",...this.g.allowedCurrencies.length>0?this.g.allowedCurrencies:this.g.currencies],this.receive.unit=this.g.isFiatPriority&&this.g.wallet.currency||"sat",this.receive.minMax=[0,21e14],this.receive.lnurl=null},onReceiveDialogHide(){this.hasNfc&&this.nfcReaderAbortController.abort()},showParseDialog(){this.parse.show=!0,this.parse.invoice=null,this.parse.lnurlpay=null,this.parse.lnurlauth=null,this.parse.copy.show=window.isSecureContext&&void 0!==navigator.clipboard?.readText,this.parse.data.request="",this.parse.data.comment="",this.parse.data.internalMemo=null,this.parse.sending=!1,this.parse.data.paymentChecker=null,this.parse.camera.show=!1},closeParseDialog(){setTimeout(()=>{clearInterval(this.parse.paymentChecker)},1e4)},handleBalanceUpdate(e){this.g.wallet.sat=this.g.wallet.sat+e},createInvoice(){this.receive.status="loading",this.g.isSatsDenomination||(this.receive.data.amount=100*this.receive.data.amount),LNbits.api.createInvoice(this.g.wallet,this.receive.data.amount,this.receive.data.memo,this.receive.unit,this.receive.lnurlWithdraw,this.receive.fiatProvider,this.receive.data.internalMemo,this.receive.data.payment_hash).then(e=>{if(this.g.updatePayments=!this.g.updatePayments,this.receive.status="success",this.receive.paymentReq=e.data.bolt11,this.receive.fiatPaymentReq=e.data.extra?.fiat_payment_request,this.receive.amountMsat=e.data.amount,this.receive.paymentHash=e.data.payment_hash,this.receive.lnurl||this.readNfcTag(),this.receive.lnurl&&null!==e.data.extra?.lnurl_response){!1===e.data.extra.lnurl_response&&(e.data.extra.lnurl_response="Unable to connect");const t=this.receive.lnurl.callback.split("/")[2];if("string"==typeof e.data.extra.lnurl_response)return void Quasar.Notify.create({timeout:5e3,type:"warning",message:`${t} lnurl-withdraw call failed.`,caption:e.data.extra.lnurl_response});!0===e.data.extra.lnurl_response&&Quasar.Notify.create({timeout:3e3,message:`Invoice sent to ${t}!`,spinner:!0})}}).catch(e=>{LNbits.utils.notifyApiError(e),this.receive.status="pending"})},lnurlScan(){LNbits.api.request("POST","/api/v1/lnurlscan",this.g.wallet.adminkey,{lnurl:this.parse.data.request}).then(e=>{const t=e.data;if("ERROR"!==t.status){if("payRequest"===t.tag)this.parse.lnurlpay=Object.freeze(t),this.parse.data.amount=t.minSendable/1e3,this.receive.units=["sats",...this.g.allowedCurrencies.length>0?this.g.allowedCurrencies:this.g.currencies];else if("login"===t.tag)this.parse.lnurlauth=Object.freeze(t);else if("withdrawRequest"===t.tag){this.parse.show=!1,this.receive.show=!0,this.receive.lnurlWithdraw=Object.freeze(t),this.receive.status="pending",this.receive.paymentReq=null,this.receive.paymentHash=null,this.receive.data.amount=t.maxWithdrawable/1e3,this.receive.data.memo=t.defaultDescription,this.receive.minMax=[t.minWithdrawable/1e3,t.maxWithdrawable/1e3];const e=t.callback.split("/")[2];this.receive.lnurl={domain:e,callback:t.callback,fixed:t.fixed}}}else Quasar.Notify.create({timeout:5e3,type:"warning",message:"lnurl scan failed.",caption:t.reason})}).catch(e=>{LNbits.utils.notifyApiError(e)})},decodeQR(e){this.parse.data.request=e,this.decodeRequest(),this.parse.camera.show=!1},isLnurl:e=>e.toLowerCase().startsWith("lnurl1")||e.startsWith("lnurlp://")||e.startsWith("lnurlw://")||e.startsWith("lnurlauth://")||e.match(/[\w.+-~_]+@[\w.+-~_]/),decodeRequest(){this.parse.show=!0,this.parse.data.request=this.parse.data.request.trim();const e=this.parse.data.request.toLowerCase();if(e.startsWith("lightning:")?this.parse.data.request=this.parse.data.request.slice(10):e.startsWith("lnurl:")?this.parse.data.request=this.parse.data.request.slice(6):e.includes("lightning=lnurl1")&&(this.parse.data.request=this.parse.data.request.split("lightning=")[1].split("&")[0]),this.isLnurl(this.parse.data.request))return void this.lnurlScan();let t;this.parse.data.request.toLowerCase().includes("lightning")&&(this.parse.data.request=this.parse.data.request.split("lightning=")[1],this.parse.data.request.includes("&")&&(this.parse.data.request=this.parse.data.request.split("&")[0]));try{t=decode(this.parse.data.request)}catch(e){return Quasar.Notify.create({timeout:3e3,type:"warning",message:e+".",caption:"400 BAD REQUEST"}),void(this.parse.show=!1)}let s={msat:t.human_readable_part.amount,sat:t.human_readable_part.amount/1e3,fsat:LNbits.utils.formatSat(t.human_readable_part.amount/1e3),bolt11:this.parse.data.request};_.each(t.data.tags,e=>{if(_.isObject(e)&&_.has(e,"description"))if("payment_hash"===e.description)s.hash=e.value;else if("description"===e.description)s.description=e.value;else if("expiry"===e.description){const a=new Date(1e3*(t.data.time_stamp+e.value)),i=new Date(1e3*t.data.time_stamp);s.expireDate=Quasar.date.formatDate(a,"YYYY-MM-DDTHH:mm:ss.SSSZ"),s.createdDate=Quasar.date.formatDate(i,"YYYY-MM-DDTHH:mm:ss.SSSZ"),s.expireDateFrom=moment.utc(a).local().fromNow(),s.createdDateFrom=moment.utc(i).local().fromNow(),s.expired=!1}}),this.g.wallet.currency&&(s.fiatAmount=LNbits.utils.formatCurrency((s.sat/1e8*this.g.exchangeRate).toFixed(2),this.g.wallet.currency)),this.parse.invoice=Object.freeze(s)},payInvoice(){if(this.parse.sending)return;this.parse.sending=!0;const e=Quasar.Notify.create({timeout:0,message:this.$t("payment_processing")});LNbits.api.payInvoice(this.g.wallet,this.parse.data.request,this.parse.data.internalMemo).then(t=>{this.parse.sending=!1,e(),this.g.updatePayments=!this.g.updatePayments,this.parse.show=!1,"success"==t.data.status&&Quasar.Notify.create({type:"positive",message:this.$t("payment_successful")}),"pending"==t.data.status&&Quasar.Notify.create({type:"info",message:this.$t("payment_pending")})}).catch(t=>{this.parse.sending=!1,e(),LNbits.utils.notifyApiError(t),this.g.updatePayments=!this.g.updatePayments})},payLnurl(){this.parse.sending||(this.parse.sending=!0,LNbits.api.request("post","/api/v1/payments/lnurl",this.g.wallet.adminkey,{res:this.parse.lnurlpay,lnurl:this.parse.data.request,unit:this.parse.data.unit,amount:1e3*this.parse.data.amount,comment:this.parse.data.comment,internalMemo:this.parse.data.internalMemo}).then(e=>{if(this.parse.sending=!1,this.parse.show=!1,e.data.extra.success_action){const t=JSON.parse(e.data.extra.success_action);switch(t.tag){case"url":Quasar.Notify.create({message:t.url,caption:t.description,html:!1,type:"positive",timeout:0,closeBtn:!0,actions:[{label:"Open link",color:"white",handler:()=>this.utils.openUrlInNewTab(t.url)}]});break;case"message":Quasar.Notify.create({message:t.message,type:"positive",timeout:0,closeBtn:!0});break;case"aes":this.utils.decryptLnurlPayAES(t,e.data.preimage).then(e=>{Quasar.Notify.create({message:e,caption:t.description,html:!1,type:"positive",timeout:0,closeBtn:!0})}).catch(e=>{Quasar.Notify.create({message:t.description||"Payment successful.",caption:"Could not decrypt success action.",html:!1,type:"warning",timeout:0,closeBtn:!0})})}}}).catch(e=>{this.parse.sending=!1,LNbits.utils.notifyApiError(e)}))},authLnurl(){const e=Quasar.Notify.create({timeout:10,message:"Performing authentication..."});LNbits.api.request("post","/api/v1/lnurlauth",wallet.adminkey,this.parse.lnurlauth).then(t=>{e(),Quasar.Notify.create({message:"Authentication successful.",type:"positive",timeout:3500}),this.parse.show=!1}).catch(e=>{e.response.data.reason?Quasar.Notify.create({message:`Authentication failed. ${this.parse.lnurlauth.callback} says:`,caption:e.response.data.reason,type:"warning",timeout:5e3}):LNbits.utils.notifyApiError(e)})},updateWallet(e){LNbits.api.request("PATCH","/api/v1/wallet",this.g.wallet.adminkey,e).then(e=>{this.g.wallet={...this.g.wallet,...e.data};const t=this.g.user.wallets.findIndex(t=>t.id===e.data.id);-1!==t&&(this.g.user.wallets[t]={...this.g.user.wallets[t],...e.data}),Quasar.Notify.create({message:"Wallet updated.",type:"positive",timeout:3500})}).catch(e=>{LNbits.utils.notifyApiError(e)})},pasteToTextArea(){this.$refs.textArea.focus(),navigator.clipboard.readText().then(e=>{this.parse.data.request=e.trim()})},readNfcTag(){try{if("undefined"==typeof NDEFReader)return void console.debug("NFC not supported on this device or browser.");const e=new NDEFReader;this.nfcReaderAbortController=new AbortController,this.nfcReaderAbortController.signal.onabort=e=>{console.debug("All NFC Read operations have been aborted.")},this.hasNfc=!0;const t=Quasar.Notify.create({message:"Tap your NFC tag to pay this invoice with LNURLw."});return e.scan({signal:this.nfcReaderAbortController.signal}).then(()=>{e.onreadingerror=()=>{Quasar.Notify.create({type:"negative",message:"There was an error reading this NFC tag."})},e.onreading=({message:e})=>{const s=new TextDecoder("utf-8"),a=e.records.find(e=>-1!==s.decode(e.data).toUpperCase().indexOf("LNURLW"));if(a){t(),Quasar.Notify.create({type:"positive",message:"NFC tag read successfully."});const e=s.decode(a.data);this.payInvoiceWithNfc(e)}else Quasar.Notify.create({type:"warning",message:"NFC tag does not have LNURLw record."})}})}catch(e){Quasar.Notify.create({type:"negative",message:e?e.toString():"An unexpected error has occurred."})}},payInvoiceWithNfc(e){const t=Quasar.Notify.create({timeout:0,spinner:!0,message:this.$t("processing_payment")});LNbits.api.request("POST",`/api/v1/payments/${this.receive.paymentReq}/pay-with-nfc`,this.g.wallet.adminkey,{lnurl_w:e}).then(e=>{t(),e.data.success?Quasar.Notify.create({type:"positive",message:"Payment successful"}):Quasar.Notify.create({type:"negative",message:e.data.detail||"Payment failed"})}).catch(e=>{t(),LNbits.utils.notifyApiError(e)})}},created(){const e=new URLSearchParams(window.location.search);(e.has("lightning")||e.has("lnurl"))&&(this.parse.data.request=e.get("lightning")||e.get("lnurl"),this.decodeRequest(),this.parse.show=!0);const t=this.g.user.wallets.find(e=>e.id===this.$route.params.id);t?(this.g.wallet=t,this.g.lastActiveWallet=t.id,this.$q.localStorage.setItem("lnbits.lastActiveWallet",t.id),this.$router.replace(`/wallet/${t.id}`)):(this.g.errorCode=404,this.g.errorMessage="Wallet not found.",this.$router.push("/error"))},watch:{"g.updatePaymentsHash"(){this.receive.show=!1},"g.updatePayments"(){this.parse.show=!1,this.g.wallet.currency&&this.$q.localStorage.getItem("lnbits.exchangeRate."+this.g.wallet.currency)&&(this.g.exchangeRate=this.$q.localStorage.getItem("lnbits.exchangeRate."+this.g.wallet.currency),this.g.fiatBalance=this.g.exchangeRate/1e8*this.g.wallet.sat)},"g.wallet"(){this.g.wallet.currency?(this.g.fiatTracking=!0,this.g.fiatBalance=this.g.exchangeRate/1e8*this.g.wallet.sat):(this.g.fiatBalance=0,this.g.fiatTracking=!1)},"g.isFiatPriority"(){this.receive.unit=this.g.isFiatPriority?this.g.wallet.currency:"sat"},"g.fiatBalance"(){this.formattedFiatAmount=LNbits.utils.formatCurrency(this.g.fiatBalance.toFixed(2),this.g.wallet.currency)},"g.exchangeRate"(){this.g.fiatTracking&&this.g.wallet.currency&&(this.g.fiatBalance=this.g.exchangeRate/1e8*this.g.wallet.sat)}}},window.PageWallets={template:"#page-wallets",data:()=>({user:null,tab:"wallets",wallets:[],addWalletDialog:{show:!1},walletsTable:{columns:[{name:"name",align:"left",label:"Name",field:"name",sortable:!0},{name:"currency",align:"center",label:"Currency",field:"currency",sortable:!0},{name:"updated_at",align:"right",label:"Last Updated",field:"updated_at",sortable:!0}],pagination:{sortBy:"updated_at",rowsPerPage:12,page:1,descending:!0,rowsNumber:10},search:"",hideEmpty:!0,loading:!1}}),watch:{"walletsTable.search":{handler(){const e={};this.walletsTable.search&&(e.search=this.walletsTable.search),this.getUserWallets()}}},methods:{async getUserWallets(e){try{this.walletsTable.loading=!0;const t=LNbits.utils.prepareFilterQuery(this.walletsTable,e),{data:s}=await LNbits.api.request("GET",`/api/v1/wallet/paginated?${t}`,null);this.wallets=s.data,this.walletsTable.pagination.rowsNumber=s.total}catch(e){LNbits.utils.notifyApiError(e)}finally{this.walletsTable.loading=!1}},goToWallet(e){this.$router.push({path:"/wallet",query:{wal:e}})},formattedFiatAmount:(e,t)=>LNbits.utils.formatCurrency(Number(e).toFixed(2),t),formattedSatAmount:e=>LNbits.utils.formatMsat(e)+" sat"},async created(){await this.getUserWallets()}},window.PageUsers={template:"#page-users",data(){return{paymentsWallet:{},cancel:{},users:[],wallets:[],searchData:{user:"",username:"",email:"",pubkey:""},paymentPage:{show:!1},activeWallet:{userId:null,show:!1},activeUser:{data:null,showUserId:!1,show:!1},createWalletDialog:{data:{},show:!1},walletTable:{columns:[{name:"name",align:"left",label:"Name",field:"name"},{name:"id",align:"left",label:"Wallet Id",field:"id"},{name:"currency",align:"left",label:"Currency",field:"currency"},{name:"balance_msat",align:"left",label:"Balance",field:"balance_msat"}],pagination:{sortBy:"name",rowsPerPage:10,page:1,descending:!0,rowsNumber:10},search:null,hideEmpty:!0,loading:!1},usersTable:{columns:[{name:"activated",align:"left",label:this.$t("activated"),field:"activated",sortable:!1},{name:"wallet_id",align:"left",label:"Wallets",field:"wallet_id",sortable:!1},{name:"id",align:"left",label:"User Id",field:"id",sortable:!1},{name:"username",align:"left",label:"Username",field:"username",sortable:!1},{name:"email",align:"left",label:"Email",field:"email",sortable:!1},{name:"pubkey",align:"left",label:"Public Key",field:"pubkey",sortable:!1},{name:"balance_msat",align:"left",label:"Balance",field:"balance_msat",sortable:!1},{name:"transaction_count",align:"left",label:"Payments",field:"transaction_count",sortable:!1},{name:"last_payment",align:"left",label:"Last Payment",field:"last_payment",sortable:!1}],pagination:{sortBy:"created_at",rowsPerPage:10,page:1,descending:!0,rowsNumber:10},sortFields:[{name:"id",label:"User ID"},{name:"username",label:"Username"},{name:"email",label:"Email"},{name:"pubkey",label:"Public Key"},{name:"created_at",label:"Creation Date"},{name:"updated_at",label:"Last Updated"}],search:null,hideEmpty:!0,loading:!1}}},watch:{"usersTable.hideEmpty":function(e,t){this.usersTable.filter=e?{"transaction_count[gt]":0}:{},this.fetchUsers()}},created(){this.fetchUsers()},methods:{formatSat:e=>LNbits.utils.formatSat(Math.floor(e/1e3)),backToUsersPage(){this.activeUser.show=!1,this.paymentPage.show=!1,this.activeWallet.show=!1,this.fetchUsers()},handleBalanceUpdate(){this.fetchWallets(this.activeWallet.userId)},resetPassword(e){return LNbits.api.request("PUT",`/users/api/v1/user/${e}/reset_password`).then(e=>{LNbits.utils.confirmDialog(this.$t("reset_key_generated")+" "+this.$t("reset_key_copy")).onOk(()=>{const t=window.location.origin+"?reset_key="+e.data;this.utils.copyText(t)})}).catch(LNbits.utils.notifyApiError)},sortByColumn(e){this.usersTable.pagination.sortBy===e?this.usersTable.pagination.descending=!this.usersTable.pagination.descending:(this.usersTable.pagination.sortBy=e,this.usersTable.pagination.descending=!1),this.fetchUsers()},createUser(){LNbits.api.request("POST","/users/api/v1/user",null,this.activeUser.data).then(e=>{Quasar.Notify.create({type:"positive",message:"User created!",icon:null}),this.activeUser.setPassword=!0,this.activeUser.data=e.data,this.fetchUsers()}).catch(LNbits.utils.notifyApiError)},updateUser(){LNbits.api.request("PUT",`/users/api/v1/user/${this.activeUser.data.id}`,null,this.activeUser.data).then(()=>{Quasar.Notify.create({type:"positive",message:"User updated!",icon:null}),this.activeUser.data=null,this.activeUser.show=!1,this.fetchUsers()}).catch(LNbits.utils.notifyApiError)},createWallet(){const e=this.activeWallet.userId;e?LNbits.api.request("POST",`/users/api/v1/user/${e}/wallet`,null,this.createWalletDialog.data).then(()=>{this.fetchWallets(e),Quasar.Notify.create({type:"positive",message:"Wallet created!"})}).catch(LNbits.utils.notifyApiError):Quasar.Notify.create({type:"warning",message:"No user selected!",icon:null})},deleteUser(e){LNbits.utils.confirmDialog("Are you sure you want to delete this user?").onOk(()=>{LNbits.api.request("DELETE",`/users/api/v1/user/${e}`).then(()=>{this.fetchUsers(),Quasar.Notify.create({type:"positive",message:"User deleted!",icon:null}),this.activeUser.data=null,this.activeUser.show=!1}).catch(LNbits.utils.notifyApiError)})},undeleteUserWallet(e,t){LNbits.api.request("PUT",`/users/api/v1/user/${e}/wallet/${t}/undelete`).then(()=>{this.fetchWallets(e),Quasar.Notify.create({type:"positive",message:"Undeleted user wallet!",icon:null})}).catch(LNbits.utils.notifyApiError)},deleteUserWallet(e,t,s){const a=s?"Wallet is already deleted, are you sure you want to permanently delete this user wallet?":"Are you sure you want to delete this user wallet?";LNbits.utils.confirmDialog(a).onOk(()=>{LNbits.api.request("DELETE",`/users/api/v1/user/${e}/wallet/${t}`).then(()=>{this.fetchWallets(e),Quasar.Notify.create({type:"positive",message:"User wallet deleted!",icon:null})}).catch(LNbits.utils.notifyApiError)})},deleteAllUserWallets(e){LNbits.utils.confirmDialog(this.$t("confirm_delete_all_wallets")).onOk(()=>{LNbits.api.request("DELETE",`/users/api/v1/user/${e}/wallets`).then(t=>{Quasar.Notify.create({type:"positive",message:t.data.message,icon:null}),this.fetchWallets(e)}).catch(LNbits.utils.notifyApiError)})},copyWalletLink(e){const t=`${window.location.origin}/wallet?usr=${this.activeWallet.userId}&wal=${e}`;this.utils.copyText(t)},fetchUsers(e){this.relaxFilterForFields(["username","email"]);const t=LNbits.utils.prepareFilterQuery(this.usersTable,e);LNbits.api.request("GET",`/users/api/v1/user?${t}`).then(e=>{this.usersTable.loading=!1,this.usersTable.pagination.rowsNumber=e.data.total,this.users=e.data.data}).catch(LNbits.utils.notifyApiError)},fetchWallets(e){return LNbits.api.request("GET",`/users/api/v1/user/${e}/wallet`).then(t=>{this.wallets=t.data,this.activeWallet.userId=e,this.activeWallet.show=!0}).catch(LNbits.utils.notifyApiError)},relaxFilterForFields(e=[]){e.forEach(e=>{const t=this.usersTable?.filter?.[e];t&&this.usersTable.filter[e]&&(this.usersTable.filter[`${e}[like]`]=t,delete this.usersTable.filter[e])})},updateWallet(e){LNbits.api.request("PATCH","/api/v1/wallet",e.adminkey,{name:e.name}).then(()=>{e.editable=!1,Quasar.Notify.create({message:"Wallet name updated.",type:"positive",timeout:3500})}).catch(e=>{LNbits.utils.notifyApiError(e)})},toggleAdmin(e){LNbits.api.request("PUT",`/users/api/v1/user/${e}/admin`).then(()=>{this.fetchUsers(),Quasar.Notify.create({type:"positive",message:"Toggled admin!",icon:null})}).catch(LNbits.utils.notifyApiError)},toggleUserActivated(e){LNbits.api.request("PUT",`/users/api/v1/user/${e}/activate`).then(e=>{this.fetchUsers(),Quasar.Notify.create({type:"positive",message:e.data.message,icon:null})}).catch(LNbits.utils.notifyApiError)},async showAccountPage(e){if(this.activeUser.showPassword=!1,this.activeUser.showUserId=!1,this.activeUser.setPassword=!1,!e)return this.activeUser.data={extra:{}},void(this.activeUser.show=!0);try{const{data:t}=await LNbits.api.request("GET",`/users/api/v1/user/${e}`);this.activeUser.data=t,this.activeUser.show=!0}catch(e){console.warn(e),Quasar.Notify.create({type:"warning",message:"Failed to get user!"}),this.activeUser.show=!1}},async impersonateUser(e){try{await LNbits.api.impersonateUser(e),LNbits.utils.backupLocalStorage("impersonation",!0),this.$q.localStorage.setItem("lnbits.disclaimerShown",!0),window.location="/wallet"}catch(e){console.warn(e),Quasar.Notify.create({type:"warning",message:"Failed to impersonate user!"})}},async showWalletPayments(e){this.activeUser.show=!1,await this.fetchWallets(this.users[0].id),await this.showPayments(e)},showPayments(e){this.paymentsWallet=this.wallets.find(t=>t.id===e),this.paymentPage.show=!0},searchUserBy(e){const t=this.searchData[e];this.usersTable.filter={},t&&(this.usersTable.filter[e]=t),this.fetchUsers()},shortify:e=>(valueLength=(e||"").length,valueLength<=10?e:`${e.substring(0,5)}...${e.substring(valueLength-5,valueLength)}`)}},window.PageAccount={template:"#page-account",data(){return{untouchedUser:null,hasUsername:!1,showUserId:!1,themeOptions:[{name:"bitcoin",color:"deep-orange"},{name:"classic",color:"purple"},{name:"mint",color:"green"},{name:"autumn",color:"brown"},{name:"monochrome",color:"grey"},{name:"salvador",color:"blue-10"},{name:"freedom",color:"pink-13"},{name:"cyber",color:"light-green-9"},{name:"flamingo",color:"pink-3"}],defaultSiteCustomisation:{locale:"en"},reactionOptions:["None","confettiBothSides","confettiFireworks","confettiStars","confettiTop"],borderOptions:["retro-border","hard-border","neon-border","no-border"],tab:"user",credentialsData:{show:!1,oldPassword:null,newPassword:null,newPasswordRepeat:null,username:null,pubkey:null},apiAcl:{showNewAclDialog:!1,showPasswordDialog:!1,showNewTokenDialog:!1,data:[],passwordGuardedFunction:null,newAclName:"",newTokenName:"",password:"",apiToken:null,selectedTokenId:null,columns:[{name:"Name",align:"left",label:this.$t("Name"),field:"Name",sortable:!1},{name:"path",align:"left",label:this.$t("path"),field:"path",sortable:!1},{name:"read",align:"left",label:this.$t("read"),field:"read",sortable:!1},{name:"write",align:"left",label:this.$t("write"),field:"write",sortable:!1}],pagination:{rowsPerPage:100,page:1}},selectedApiAcl:{id:null,name:null,endpoints:[],token_id_list:[],allRead:!1,allWrite:!1},assets:[],assetsTable:{loading:!1,columns:[{name:"name",align:"left",label:this.$t("Name"),field:"name",sortable:!0},{name:"created_at",align:"left",label:this.$t("created_at"),field:"created_at",sortable:!0}],pagination:{rowsPerPage:6,page:1}},assetsUploadToPublic:!1,notifications:{nostr:{identifier:""}},labels:[],labelsDialog:{show:!1,data:{name:"",description:"",color:"#000000"}},labelsTable:{loading:!1,columns:[{name:"actions",align:"left"},{name:"name",align:"left",label:this.$t("Name"),field:"name",sortable:!0},{name:"description",align:"left",label:this.$t("description"),field:"description"},{name:"color",align:"left",label:this.$t("color"),field:"color"}],pagination:{rowsPerPage:6,page:1}}}},watch:{tab(e){this.$router.push(`/account#${e}`)},$route(e){e.hash.length>1&&(this.tab=e.hash.replace("#",""))},"assetsTable.search":{handler(){const e={};this.assetsTable.search&&(e.search=this.assetsTable.search),this.getUserAssets()}}},computed:{isUserTouched(){return!_.isEqual(this.g.user,this.untouchedUser)}},methods:{changeLanguage(e){window.i18n.global.locale=e,this.$q.localStorage.set("lnbits.lang",e)},async updateAccount(){try{const{data:e}=await LNbits.api.request("PATCH","/api/v1/auth",null,{user_id:this.g.user.id,username:this.g.user.username,email:this.g.user.email,extra:this.g.user.extra});this.untouchedUser=JSON.parse(JSON.stringify(this.g.user)),this.hasUsername=!!e.username,Quasar.Notify.create({type:"positive",message:"Account updated."})}catch(e){LNbits.utils.notifyApiError(e)}},disableUpdatePassword(){return!this.credentialsData.newPassword||!this.credentialsData.newPasswordRepeat||this.credentialsData.newPassword!==this.credentialsData.newPasswordRepeat},async updatePassword(){if(this.credentialsData.username)try{const{data:e}=await LNbits.api.request("PUT","/api/v1/auth/password",null,{user_id:this.g.user.id,username:this.credentialsData.username,password_old:this.credentialsData.oldPassword,password:this.credentialsData.newPassword,password_repeat:this.credentialsData.newPasswordRepeat});this.untouchedUser=JSON.parse(JSON.stringify(e)),this.hasUsername=!!e.username,this.credentialsData.show=!1,Quasar.Notify.create({type:"positive",message:"Password updated."})}catch(e){LNbits.utils.notifyApiError(e)}else Quasar.Notify.create({type:"warning",message:"Please set a username."})},async updatePubkey(){try{const{data:e}=await LNbits.api.request("PUT","/api/v1/auth/pubkey",null,{user_id:this.g.user.id,pubkey:this.credentialsData.pubkey});this.untouchedUser=JSON.parse(JSON.stringify(e)),this.hasUsername=!!e.username,this.credentialsData.show=!1,this.$q.notify({type:"positive",message:"Public key updated."})}catch(e){LNbits.utils.notifyApiError(e)}},showUpdateCredentials(){this.credentialsData={show:!0,oldPassword:null,username:this.g.user.username,pubkey:this.g.user.pubkey,newPassword:null,newPasswordRepeat:null}},newApiAclDialog(){this.apiAcl.newAclName=null,this.apiAcl.showNewAclDialog=!0},newTokenAclDialog(){this.apiAcl.newTokenName=null,this.apiAcl.newTokenExpiry=null,this.apiAcl.showNewTokenDialog=!0},handleApiACLSelected(e){this.selectedApiAcl={id:null,name:null,endpoints:[],token_id_list:[]},this.apiAcl.selectedTokenId=null,e&&setTimeout(()=>{const t=this.apiAcl.data.find(t=>t.id===e);this.selectedApiAcl&&(this.selectedApiAcl={...t},this.selectedApiAcl.allRead=this.selectedApiAcl.endpoints.every(e=>e.read),this.selectedApiAcl.allWrite=this.selectedApiAcl.endpoints.every(e=>e.write))})},handleAllEndpointsReadAccess(){this.selectedApiAcl.endpoints.forEach(e=>e.read=this.selectedApiAcl.allRead)},handleAllEndpointsWriteAccess(){this.selectedApiAcl.endpoints.forEach(e=>e.write=this.selectedApiAcl.allWrite)},async getApiACLs(){try{const{data:e}=await LNbits.api.request("GET","/api/v1/auth/acl",null);this.apiAcl.data=e.access_control_list}catch(e){LNbits.utils.notifyApiError(e)}},askPasswordAndRunFunction(e){this.apiAcl.passwordGuardedFunction=e,this.apiAcl.showPasswordDialog=!0},runPasswordGuardedFunction(){this.apiAcl.showPasswordDialog=!1;const e=this.apiAcl.passwordGuardedFunction;e&&this[e]()},async addApiACL(){if(this.apiAcl.newAclName){try{const{data:e}=await LNbits.api.request("PUT","/api/v1/auth/acl",null,{id:this.apiAcl.newAclName,name:this.apiAcl.newAclName,password:this.apiAcl.password});this.apiAcl.data=e.access_control_list;const t=this.apiAcl.data.find(e=>e.name===this.apiAcl.newAclName);this.handleApiACLSelected(t.id),this.apiAcl.showNewAclDialog=!1,this.$q.notify({type:"positive",message:"Access Control List created."})}catch(e){LNbits.utils.notifyApiError(e)}finally{this.apiAcl.name="",this.apiAcl.password=""}this.apiAcl.showNewAclDialog=!1}else this.$q.notify({type:"warning",message:"Name is required."})},async updateApiACLs(){try{const{data:e}=await LNbits.api.request("PUT","/api/v1/auth/acl",null,{id:this.g.user.id,password:this.apiAcl.password,...this.selectedApiAcl});this.apiAcl.data=e.access_control_list}catch(e){LNbits.utils.notifyApiError(e)}finally{this.apiAcl.password=""}},async deleteApiACL(){if(this.selectedApiAcl.id){try{await LNbits.api.request("DELETE","/api/v1/auth/acl",null,{id:this.selectedApiAcl.id,password:this.apiAcl.password}),this.$q.notify({type:"positive",message:"Access Control List deleted."})}catch(e){LNbits.utils.notifyApiError(e)}finally{this.apiAcl.password=""}this.apiAcl.data=this.apiAcl.data.filter(e=>e.id!==this.selectedApiAcl.id),this.handleApiACLSelected(this.apiAcl.data[0]?.id)}},async generateApiToken(){if(!this.selectedApiAcl.id)return;const e=new Date(this.apiAcl.newTokenExpiry)-new Date;try{const{data:t}=await LNbits.api.request("POST","/api/v1/auth/acl/token",null,{acl_id:this.selectedApiAcl.id,token_name:this.apiAcl.newTokenName,password:this.apiAcl.password,expiration_time_minutes:Math.trunc(e/6e4)});this.apiAcl.apiToken=t.api_token,this.apiAcl.selectedTokenId=t.id,Quasar.Notify.create({type:"positive",message:"Token Generated."}),await this.getApiACLs(),this.handleApiACLSelected(this.selectedApiAcl.id),this.apiAcl.showNewTokenDialog=!1}catch(e){LNbits.utils.notifyApiError(e)}finally{this.apiAcl.password=""}},async deleteToken(){if(this.apiAcl.selectedTokenId)try{await LNbits.api.request("DELETE","/api/v1/auth/acl/token",null,{id:this.apiAcl.selectedTokenId,acl_id:this.selectedApiAcl.id,password:this.apiAcl.password}),this.$q.notify({type:"positive",message:"Token deleted."}),this.selectedApiAcl.token_id_list=this.selectedApiAcl.token_id_list.filter(e=>e.id!==this.apiAcl.selectedTokenId),this.apiAcl.selectedTokenId=null}catch(e){LNbits.utils.notifyApiError(e)}finally{this.apiAcl.password=""}},async getUserAssets(e){try{this.assetsTable.loading=!0;const t=LNbits.utils.prepareFilterQuery(this.assetsTable,e),{data:s}=await LNbits.api.request("GET",`/api/v1/assets/paginated?${t}`,null);this.assets=s.data,this.assetsTable.pagination.rowsNumber=s.total}catch(e){LNbits.utils.notifyApiError(e)}finally{this.assetsTable.loading=!1}},onImageInput(e){const t=e.target.files[0];t&&this.uploadAsset(t),e.target.value=null},onBackgroundImageInput(e){const t=e.target.files[0];t&&this.uploadBackgroundImage(t),e.target.value=null},async uploadAsset(e,{isPublic:t=this.assetsUploadToPublic,notifySuccess:s=!0}={}){const a=new FormData;a.append("file",e);try{const{data:e}=await LNbits.api.request("POST",`/api/v1/assets?public_asset=${t}`,null,a,{headers:{"Content-Type":"multipart/form-data"}});return s&&this.$q.notify({type:"positive",message:"Upload successful!",icon:null}),await this.getUserAssets(),e}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}},async uploadBackgroundImage(e){const t=await this.uploadAsset(e,{isPublic:!1,notifySuccess:!1});if(!t)return;const s=`${window.location.origin}/api/v1/assets/${t.id}/thumbnail`;await this.siteCustomisationChanged({bgimageChoice:s})},async deleteAsset(e){LNbits.utils.confirmDialog("Are you sure you want to delete this asset?").onOk(async()=>{try{await LNbits.api.request("DELETE",`/api/v1/assets/${e.id}`,null),this.$q.notify({type:"positive",message:"Asset deleted."}),await this.getUserAssets()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}})},async toggleAssetPublicAccess(e){try{await LNbits.api.request("PUT",`/api/v1/assets/${e.id}`,null,{is_public:!e.is_public}),this.$q.notify({type:"positive",message:"Update successful!",icon:null}),await this.getUserAssets()}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}},copyAssetLinkToClipboard(e){const t=`${window.location.origin}/api/v1/assets/${e.id}/data`;this.utils.copyText(t)},addUserLabel(){if(!this.labelsDialog.data.name)return void this.$q.notify({type:"warning",message:"Name is required."});if(!this.labelsDialog.data.color)return void this.$q.notify({type:"warning",message:"Color is required."});this.g.user.extra.labels=this.g.user.extra.labels||[];if(!this.g.user.extra.labels.find(e=>e.name===this.labelsDialog.data.name))return this.g.user.extra.labels.unshift({...this.labelsDialog.data}),this.labelsDialog.show=!1,!0;this.$q.notify({type:"warning",message:"A label with this name already exists."})},openAddLabelDialog(){this.labelsDialog.data={name:"",description:"",color:"#000000"},this.labelsDialog.show=!0},openEditLabelDialog(e){this.labelsDialog.data={name:e.name,description:e.description,color:e.color},this.labelsDialog.show=!0},updateUserLabel(){const e=this.labelsDialog.data,t=JSON.parse(JSON.stringify(this.g.user.extra.labels));this.g.user.extra.labels=this.g.user.extra.labels.filter(t=>t.name!==e.name);this.addUserLabel()||(this.g.user.extra.labels=t),this.labelsDialog.show=!1},deleteUserLabel(e){LNbits.utils.confirmDialog("Are you sure you want to delete this label?").onOk(()=>{this.g.user.extra.labels=this.g.user.extra.labels.filter(t=>t.name!==e.name)})},async siteCustomisationChanged(e={}){try{Object.entries(e||{}).forEach(([e,t])=>{e in this.g&&(this.g[e]=t)}),await LNbits.api.updateUiCustomization(e),this.$q.notify({type:"positive",message:"UI Customization updated."})}catch(e){LNbits.utils.notifyApiError(e)}},resetThemeDefaults(){const e={themeChoice:this.g.settings.defaultTheme,borderChoice:this.g.settings.defaultBorder,gradientChoice:this.g.settings.defaultGradient,bgimageChoice:this.g.settings.defaultBgimage||"",reactionChoice:this.g.settings.defaultReaction,darkChoice:this.g.settings.defaultDark,cardRoundedChoice:this.g.settings.defaultCardRounded,cardGradientChoice:this.g.settings.defaultCardGradient,cardShadowChoice:this.g.settings.defaultCardShadow,burgerMenuChoice:this.g.settings.defaultBurgerMenuBackground};this.siteCustomisationChanged(e)}},async created(){this.untouchedUser=JSON.parse(JSON.stringify(this.g.user)),this.hasUsername=!!this.g.user.username,this.$route.hash.length>1&&(this.tab=this.$route.hash.replace("#","")),await this.getApiACLs(),await this.getUserAssets(),this.themeOptions=this.themeOptions.filter(e=>this.g.settings.themeOptions.includes(e.name))}},window.PageAdmin={template:"#page-admin",data:()=>({tab:"funding",settings:{},formData:{lnbits_exchange_rate_providers:[],lnbits_audit_exclude_paths:[],lnbits_audit_include_paths:[],lnbits_audit_http_response_codes:[]},isSuperUser:!1,needsRestart:!1}),watch:{tab(e){if(["wasm-runtime","wasm-limit-config"].includes(e)&&this.$route.path.startsWith("/admin/extensions/wasm"))return;const t=this.adminRouteForTab(e);this.$route.fullPath!==t&&this.$router.push(t)},$route(e){const t=this.adminTabFromRoute(e);this.tab!==t&&(this.tab=t)}},async created(){this.tab=this.adminTabFromRoute(this.$route),await this.getSettings()},computed:{checkChanges(){return!_.isEqual(this.settings,this.formData)}},methods:{adminTabFromRoute:e=>e.path.startsWith("/admin/extensions/wasm/limits")?"wasm-limit-config":e.path.startsWith("/admin/extensions/wasm")?"wasm-runtime":e.hash.length>1?e.hash.replace("#",""):"funding",adminRouteForTab:e=>"wasm-runtime"===e?"/admin/extensions/wasm":"wasm-limit-config"===e?"/admin/extensions/wasm/limits":`/admin#${e}`,getDefaultSetting(e){LNbits.api.getDefaultSetting(e).then(t=>{this.formData[e]=t.data.default_value})},restartServer(){LNbits.api.request("GET","/admin/api/v1/restart/").then(e=>{this.$q.notify({type:"positive",message:"Success! Restarted Server",icon:null}),this.needsRestart=!1}).catch(LNbits.utils.notifyApiError)},async getSettings(){await LNbits.api.request("GET","/admin/api/v1/settings",this.g.user.wallets[0].adminkey).then(e=>{this.isSuperUser=e.data.is_super_user||!1,this.settings=e.data,this.formData={...this.settings}}).catch(LNbits.utils.notifyApiError)},updateSettings(){const e=_.omit(this.formData,["is_super_user","lnbits_allowed_funding_sources","touch"]);LNbits.api.request("PUT","/admin/api/v1/settings",this.g.user.wallets[0].adminkey,e).then(e=>{this.needsRestart=this.settings.lnbits_backend_wallet_class!==this.formData.lnbits_backend_wallet_class,this.settings=this.formData,this.formData=_.clone(this.settings),Quasar.Notify.create({type:"positive",message:"Success! Settings changed! "+(this.needsRestart?"Restart required!":""),icon:null})}).catch(LNbits.utils.notifyApiError)},deleteSettings(){LNbits.utils.confirmDialog("Are you sure you want to restore settings to default?").onOk(()=>{LNbits.api.request("DELETE","/admin/api/v1/settings").then(e=>{Quasar.Notify.create({type:"positive",message:"Success! Restored settings to defaults. Restarting...",icon:null}),this.$q.localStorage.clear()}).catch(LNbits.utils.notifyApiError)})},downloadBackup(){window.open("/admin/api/v1/backup","_blank")}}},window.app.component("lnbits-admin-funding-seed-backup",{props:["active","is-super-user","form-data","settings"],template:"#lnbits-admin-funding-seed-backup",data:()=>({dialog:{show:!1,step:1,seed:"",visible:!1,challenge:[],answers:{},error:"",confirmField:""}}),watch:{active(e){e&&this.openIfRequired()},"formData.lnbits_backend_wallet_class"(e,t){const s=this.seedBackupSource(e);t&&s&&this.formData[s.seedField]&&(this.formData[s.confirmField]=!1),this.openIfRequired()},"formData.boltz_mnemonic"(){this.formData.boltz_mnemonic_backup_confirmed=this.formData.boltz_mnemonic===this.settings.boltz_mnemonic&&this.settings.boltz_mnemonic_backup_confirmed,this.openIfRequired()},"formData.phoenixd_mnemonic"(){this.formData.phoenixd_mnemonic_backup_confirmed=this.formData.phoenixd_mnemonic===this.settings.phoenixd_mnemonic&&this.settings.phoenixd_mnemonic_backup_confirmed,this.openIfRequired()},"formData.spark_l2_mnemonic"(){this.formData.spark_l2_mnemonic_backup_confirmed=this.formData.spark_l2_mnemonic===this.settings.spark_l2_mnemonic&&this.settings.spark_l2_mnemonic_backup_confirmed,this.openIfRequired()}},computed:{seedWords(){return this.dialog.seed.split(/\s+/).filter(Boolean).map((e,t)=>({index:t,word:e}))}},created(){this.openIfRequired()},methods:{seedBackupSource(e=this.formData.lnbits_backend_wallet_class){return"BoltzWallet"===e?{seedField:"boltz_mnemonic",confirmField:"boltz_mnemonic_backup_confirmed"}:"PhoenixdWallet"===e?{seedField:"phoenixd_mnemonic",confirmField:"phoenixd_mnemonic_backup_confirmed"}:"SparkL2Wallet"===e?{seedField:"spark_l2_mnemonic",confirmField:"spark_l2_mnemonic_backup_confirmed"}:void 0},openIfRequired(){if(!this.active||!this.isSuperUser)return;const e=this.seedBackupSource();if(!e)return;const t=(this.formData[e.seedField]||"").trim(),s=this.formData[e.confirmField];!t||s||this.dialog.show||(this.dialog={show:!0,step:1,seed:t,visible:!1,challenge:[],answers:{},error:"",confirmField:e.confirmField})},prepareChallenge(){const e=this.dialog.seed.split(/\s+/).filter(Boolean),t=Math.min(4,e.length),s=_.shuffle([...Array(e.length).keys()]).slice(0,t);this.dialog.challenge=s.sort((e,t)=>e-t).map(t=>({index:t,word:e[t]})),this.dialog.answers={},this.dialog.error="",this.dialog.step=2},submitChallenge(){if(!this.dialog.challenge.every(({index:e,word:t})=>(this.dialog.answers[e]||"").trim().toLowerCase()===t.toLowerCase()))return void(this.dialog.error="One or more words are incorrect. Check your backup and try again.");const e=this.dialog.confirmField;LNbits.api.request("PATCH","/admin/api/v1/settings",this.g.user.wallets[0].adminkey,{[e]:!0}).then(()=>{this.formData[e]=!0,this.settings[e]=!0,this.dialog.show=!1,Quasar.Notify.create({type:"positive",message:"Seed backup confirmed",icon:"check"})}).catch(LNbits.utils.notifyApiError)}}}),window.app.component("lnbits-admin-funding",{props:["active","is-super-user","form-data","settings"],template:"#lnbits-admin-funding",data:()=>({auditData:[]}),created(){this.getAudit()},methods:{getAudit(){LNbits.api.request("GET","/admin/api/v1/audit",this.g.user.wallets[0].adminkey).then(e=>{this.auditData=e.data}).catch(LNbits.utils.notifyApiError)}}}),window.app.component("lnbits-admin-funding-sources",{template:"#lnbits-admin-funding-sources",props:["form-data","allowed-funding-sources"],methods:{getFundingSourceLabel(e){const t=this.rawFundingSources.find(t=>t[0]===e);return t?t[1]:e},showQRValue(e){this.qrValue=e,this.showQRDialog=!0}},computed:{fundingSources(){let e=[];for(const[t,s,a]of this.rawFundingSources){const s={};if(null!==a)for(let[e,t]of Object.entries(a))s[e]="string"==typeof t?{label:t,value:null}:t||{};e.push([t,s])}return new Map(e)},sortedAllowedFundingSources(){return this.allowedFundingSources.sort()}},data:()=>({hideInput:!0,showQRDialog:!1,qrValue:"",rawFundingSources:[["VoidWallet","Void Wallet",null],["FakeWallet","Fake Wallet",{fake_wallet_secret:"Secret",lnbits_denomination:'"sats" or 3 Letter Custom Denomination'}],["CLNRestWallet","Core Lightning Rest (plugin)",{clnrest_url:"Endpoint",clnrest_ca:"ca.pem",clnrest_cert:"server.pem",clnrest_readonly_rune:"Rune used for readonly requests",clnrest_invoice_rune:"Rune used for creating invoices",clnrest_pay_rune:"Rune used for paying invoices using pay",clnrest_renepay_rune:"Rune used for paying invoices using renepay",clnrest_last_pay_index:"Ignores any invoices paid prior to or including this index. 0 is equivalent to not specifying and negative value is invalid.",clnrest_nodeid:"Node id"}],["CoreLightningWallet","Core Lightning",{corelightning_rpc:"Endpoint",corelightning_pay_command:"Custom Pay Command"}],["CoreLightningRestWallet","Core Lightning Rest (legacy)",{corelightning_rest_url:"Endpoint",corelightning_rest_cert:"Certificate",corelightning_rest_macaroon:"Macaroon"}],["LndRestWallet","Lightning Network Daemon (LND Rest)",{lnd_rest_endpoint:"Endpoint",lnd_rest_cert:"Certificate",lnd_rest_macaroon:"Macaroon",lnd_rest_macaroon_encrypted:"Encrypted Macaroon",lnd_rest_route_hints:{advanced:!0,label:"Enable Route Hints"},lnd_rest_allow_self_payment:{advanced:!0,label:"Allow Self Payment"}}],["LndWallet","Lightning Network Daemon (LND)",{lnd_grpc_endpoint:"Endpoint",lnd_grpc_cert:"Certificate",lnd_grpc_port:"Port",lnd_grpc_macaroon:"GRPC Macaroon",lnd_grpc_invoice_macaroon:"GRPC Invoice Macaroon",lnd_grpc_admin_macaroon:"GRPC Admin Macaroon",lnd_grpc_macaroon_encrypted:"Encrypted Macaroon"}],["LnTipsWallet","LN.Tips",{lntips_api_endpoint:"Endpoint",lntips_api_key:"API Key"}],["LNPayWallet","LN Pay",{lnpay_api_endpoint:"Endpoint",lnpay_api_key:"API Key",lnpay_wallet_key:"Wallet Key"}],["EclairWallet","Eclair (ACINQ)",{eclair_url:"URL",eclair_pass:"Password"}],["LNbitsWallet","LNbits",{lnbits_endpoint:"Endpoint",lnbits_key:"Admin Key"}],["BlinkWallet","Blink",{blink_api_endpoint:"Endpoint",blink_ws_endpoint:"WebSocket",blink_token:"Key"}],["AlbyWallet","Alby",{alby_api_endpoint:"Endpoint",alby_access_token:"Key"}],["BoltzWallet","Boltz",{boltz_client_endpoint:{label:"Boltz client endpoint",value:"127.0.0.1:9002"},boltz_client_macaroon:{label:"Admin Macaroon path or hex",value:"/home/ubuntu/.boltz/macaroons/admin.macaroon"},boltz_client_cert:{label:"Certificate path or hex",value:"/home/ubuntu/.boltz/tls.cert"},boltz_mnemonic:{label:"Liquid seed phrase",hint:"Boltz will fetch once connected, but you can change later (can be opened in a liquid wallet) ",copy:!0,qrcode:!0},boltz_client_password:{label:"Wallet Password (optional)",advanced:!0}}],["ZBDWallet","ZBD",{zbd_api_endpoint:"Endpoint",zbd_api_key:"Key"}],["PhoenixdWallet","Phoenixd",{phoenixd_api_endpoint:"Endpoint",phoenixd_api_password:"Key",phoenixd_data_dir:{label:"Data Directory",hint:"Directory where phoenixd stores its data, including the seed phrase."},phoenixd_mnemonic:{label:"Phoenixd Seed Phrase",hint:"Only available if phoenixd data-dir is specified",readonly:!0,copy:!0,qrcode:!0}}],["OpenNodeWallet","OpenNode",{opennode_api_endpoint:"Endpoint",opennode_key:"Key"}],["ClicheWallet","Cliche (NBD)",{cliche_endpoint:"Endpoint"}],["SparkWallet","Spark",{spark_url:"Endpoint",spark_token:"Token"}],["SparkL2Wallet","Spark (L2)",{spark_l2_external_endpoint:{label:"External Sidecar Endpoint",hint:"Make sure to also specify the API key if your sidecar requires authentication.",value:""},spark_l2_mnemonic:{label:"External Sidecar Mnemonic",hint:"Mnemonic for the Spark wallet on the external sidecar. Required if the side car does not have its own mnemonic.",value:""},spark_l2_external_api_key:{label:"External Sidecar API Key",hint:"API key for authenticating with the external sidecar if it requires authentication.",value:""},spark_l2_network:{label:"Network",value:"MAINNET",hint:"The network to use for the Spark wallet.",advanced:!0},spark_l2_pay_wait_ms:{label:"Payment Wait Time (ms)",hint:"The time to wait for a payment to be processed before considering it failed.",advanced:!0},spark_l2_pay_poll_ms:{label:"Payment Poll Time (ms)",hint:"The time to wait between polling for payment status updates.",advanced:!0},spark_l2_stream_keepalive_ms:{label:"Stream Keepalive Time (ms)",hint:"The time to wait between sending keepalive messages to the Spark sidecar to keep the connection open.",advanced:!0}}],["NWCWallet","Nostr Wallet Connect",{nwc_pairing_url:"Pairing URL"}],["BreezSdkWallet","Breez SDK",{breez_api_key:"Breez API Key",breez_greenlight_seed:"Greenlight Seed",breez_greenlight_device_key:"Greenlight Device Key",breez_greenlight_device_cert:"Greenlight Device Cert",breez_greenlight_invite_code:"Greenlight Invite Code"}],["StrikeWallet","Strike (alpha)",{strike_api_endpoint:"API Endpoint",strike_api_key:"API Key"}],["BreezLiquidSdkWallet","Breez Liquid SDK",{breez_liquid_api_key:"Breez API Key (can be empty)",breez_liquid_seed:"Liquid seed phrase",breez_liquid_fee_offset_sat:"Offset amount in sats to increase fee limit"}]]})}),window.app.component("lnbits-admin-fiat-providers",{props:["form-data"],template:"#lnbits-admin-fiat-providers",data:()=>({formAddStripeUser:"",formAddPaypalUser:"",formAddSquareUser:"",formAddRevolutUser:"",creatingRevolutWebhook:!1,hideInputToggle:!0}),computed:{stripeWebhookUrl(){return this.formData?.stripe_payment_webhook_url||this.calculateWebhookUrl("stripe")},paypalWebhookUrl(){return this.formData?.paypal_payment_webhook_url||this.calculateWebhookUrl("paypal")},revolutWebhookUrl(){return this.formData?.revolut_payment_webhook_url||this.calculateWebhookUrl("revolut")}},watch:{formData:{handler(){this.syncWebhookUrls()},immediate:!0}},methods:{basePathFromLocation(){if("undefined"==typeof window)return"";const e=window.location.pathname.replace(/\/+$/,""),t=e.lastIndexOf("/admin");return(t>=0?e.slice(0,t):e||"")||""},calculateWebhookUrl(e){if("undefined"==typeof window)return"";const t=`${this.basePathFromLocation()}/api/v1/callback/${e}`.replace(/\/+/g,"/"),s=t.startsWith("/")?t:`/${t}`;return`${window.location.origin}${s}`},syncWebhookUrls(){this.maybeSetWebhookUrl("stripe_payment_webhook_url","stripe"),this.maybeSetWebhookUrl("paypal_payment_webhook_url","paypal"),this.maybeSetWebhookUrl("square_payment_webhook_url","square"),this.maybeSetWebhookUrl("revolut_payment_webhook_url","revolut")},maybeSetWebhookUrl(e,t){if(!this.formData)return;const s=this.calculateWebhookUrl(t),a=this.formData[e];(!a||a.includes("your-lnbits-domain-here.com"))&&s&&(this.formData[e]=s)},copyWebhookUrl(e){e&&this.copyText(e)},isClearnetWebhookUrl(e){let t;try{t=new URL(e)}catch(e){return!1}const s=t.hostname.toLowerCase();return!!["http:","https:"].includes(t.protocol)&&(!("localhost"===s||s.endsWith(".localhost")||s.endsWith(".local")||s.endsWith(".onion"))&&!(/^127\./.test(s)||/^10\./.test(s)||/^192\.168\./.test(s)||/^169\.254\./.test(s)||/^172\.(1[6-9]|2\d|3[0-1])\./.test(s)||"0.0.0.0"===s||"::1"===s))},notifyRevolutWebhookWarning(e){Quasar.Notify.create({type:"warning",message:e,icon:null,closeBtn:!0})},addStripeAllowedUser(){const e=this.formAddStripeUser||"";e.length&&!this.formData.stripe_limits.allowed_users.includes(e)&&(this.formData.stripe_limits.allowed_users=[...this.formData.stripe_limits.allowed_users,e],this.formAddStripeUser="")},removeStripeAllowedUser(e){this.formData.stripe_limits.allowed_users=this.formData.stripe_limits.allowed_users.filter(t=>t!==e)},addPaypalAllowedUser(){const e=this.formAddPaypalUser||"";e.length&&!this.formData.paypal_limits.allowed_users.includes(e)&&(this.formData.paypal_limits.allowed_users=[...this.formData.paypal_limits.allowed_users,e],this.formAddPaypalUser="")},removePaypalAllowedUser(e){this.formData.paypal_limits.allowed_users=this.formData.paypal_limits.allowed_users.filter(t=>t!==e)},addSquareAllowedUser(){const e=this.formAddSquareUser||"";e.length&&!this.formData.square_limits.allowed_users.includes(e)&&(this.formData.square_limits.allowed_users=[...this.formData.square_limits.allowed_users,e],this.formAddSquareUser="")},removeSquareAllowedUser(e){this.formData.square_limits.allowed_users=this.formData.square_limits.allowed_users.filter(t=>t!==e)},addRevolutAllowedUser(){const e=this.formAddRevolutUser||"";e.length&&!this.formData.revolut_limits.allowed_users.includes(e)&&(this.formData.revolut_limits.allowed_users=[...this.formData.revolut_limits.allowed_users,e],this.formAddRevolutUser="")},removeRevolutAllowedUser(e){this.formData.revolut_limits.allowed_users=this.formData.revolut_limits.allowed_users.filter(t=>t!==e)},checkFiatProvider(e){LNbits.api.request("PUT",`/api/v1/fiat/check/${e}`).then(e=>{const t=e.data;Quasar.Notify.create({type:t.success?"positive":"warning",message:t.message,icon:null})}).catch(LNbits.utils.notifyApiError)},createRevolutWebhook(){const e=this.calculateWebhookUrl("revolut");this.formData.revolut_payment_webhook_url=e,this.formData.revolut_api_secret_key?this.isClearnetWebhookUrl(e)?(this.creatingRevolutWebhook=!0,LNbits.api.request("POST","/api/v1/fiat/revolut/webhook",null,{url:e,endpoint:this.formData.revolut_api_endpoint,api_secret_key:this.formData.revolut_api_secret_key,api_version:this.formData.revolut_api_version}).then(e=>{const t=e.data;this.formData.revolut_payment_webhook_url=t.url,this.formData.revolut_webhook_signing_secret=t.signing_secret,Quasar.Notify.create({type:"positive",message:`Revolut webhook ${t.already_exists?"already exists":"created"}${t.id?`: ${t.id}`:""}.`,icon:null})}).catch(LNbits.utils.notifyApiError).finally(()=>{this.creatingRevolutWebhook=!1})):this.notifyRevolutWebhookWarning("Revolut webhook URL must be a clearnet URL."):this.notifyRevolutWebhookWarning("Add your Revolut API secret key before creating a webhook.")}}}),window.app.component("lnbits-admin-exchange-providers",{props:["form-data"],template:"#lnbits-admin-exchange-providers",data:()=>({exchangeData:{selectedProvider:null,showTickerConversion:!1,convertFromTicker:null,convertToTicker:null},exchangesTable:{columns:[{name:"name",align:"left",label:"Exchange Name",field:"name",sortable:!0},{name:"api_url",align:"left",label:"URL",field:"api_url",sortable:!1},{name:"path",align:"left",label:"JSON Path",field:"path",sortable:!1},{name:"exclude_to",align:"left",label:"Exclude Currencies",field:"exclude_to",sortable:!1},{name:"ticker_conversion",align:"left",label:"Ticker Conversion",field:"ticker_conversion",sortable:!1}],pagination:{sortBy:"name",rowsPerPage:100,page:1,rowsNumber:100},search:null,hideEmpty:!0}}),mounted(){this.getExchangeRateHistory()},methods:{getDefaultSetting(e){LNbits.api.getDefaultSetting(e).then(t=>{this.formData[e]=t.data.default_value})},getExchangeRateHistory(){LNbits.api.request("GET","/api/v1/rate/history",this.g.user.wallets[0].inkey).then(e=>{this.initExchangeChart(e.data)}).catch(function(e){LNbits.utils.notifyApiError(e)})},showExchangeProvidersTab(e){"exchange_providers"===e&&this.getExchangeRateHistory()},addExchangeProvider(){this.formData.lnbits_exchange_rate_providers=[{name:"",api_url:"",path:"",exclude_to:[]},...this.formData.lnbits_exchange_rate_providers]},removeExchangeProvider(e){this.formData.lnbits_exchange_rate_providers=this.formData.lnbits_exchange_rate_providers.filter(t=>t!==e)},removeExchangeTickerConversion(e,t){e.ticker_conversion=e.ticker_conversion.filter(e=>e!==t),this.formData.touch=null},addExchangeTickerConversion(){this.exchangeData.selectedProvider&&(this.exchangeData.selectedProvider.ticker_conversion.push(`${this.exchangeData.convertFromTicker}:${this.exchangeData.convertToTicker}`),this.formData.touch=null,this.exchangeData.showTickerConversion=!1)},showTickerConversionDialog(e){this.exchangeData.convertFromTicker=null,this.exchangeData.convertToTicker=null,this.exchangeData.selectedProvider=e,this.exchangeData.showTickerConversion=!0},initExchangeChart(e){this.exchangeRatesChart&&(this.exchangeRatesChart.destroy(),this.exchangeRatesChart=null);const t=e.map(e=>this.utils.formatTimestamp(e.timestamp,"HH:mm")),s=(this.formData.lnbits_price_aggregator_enabled?[{name:"Aggregator"}]:[...this.formData.lnbits_exchange_rate_providers,{name:"LNbits"}]).map(t=>({label:t.name,data:e.map(e=>e.rates[t.name]),pointStyle:!0,borderWidth:"LNbits"===t.name?4:2,tension:.4}));this.exchangeRatesChart=new Chart(this.$refs.exchangeRatesChart.getContext("2d"),{type:"line",options:{plugins:{legend:{display:!0},title:{display:!0,text:"Bitcoin Price History"}}},data:{labels:t,datasets:s}})}}}),window.app.component("lnbits-admin-security",{props:["form-data"],template:"#lnbits-admin-security",data:()=>({logs:[],formBlockedIPs:"",serverlogEnabled:!1,nostrAcceptedUrl:"",formAllowedIPs:"",formCallbackUrlRule:""}),created(){},methods:{addAllowedIPs(){const e=this.formAllowedIPs.trim(),t=this.formData.lnbits_allowed_ips;e&&e.length&&!t.includes(e)&&(this.formData.lnbits_allowed_ips=[...t,e],this.formAllowedIPs="")},removeAllowedIPs(e){const t=this.formData.lnbits_allowed_ips;this.formData.lnbits_allowed_ips=t.filter(t=>t!==e)},addBlockedIPs(){const e=this.formBlockedIPs.trim(),t=this.formData.lnbits_blocked_ips;e&&e.length&&!t.includes(e)&&(this.formData.lnbits_blocked_ips=[...t,e],this.formBlockedIPs="")},removeBlockedIPs(e){const t=this.formData.lnbits_blocked_ips;this.formData.lnbits_blocked_ips=t.filter(t=>t!==e)},addCallbackUrlRule(){const e=this.formCallbackUrlRule.trim(),t=this.formData.lnbits_callback_url_rules;e&&e.length&&!t.includes(e)&&(this.formData.lnbits_callback_url_rules=[...t,e],this.formCallbackUrlRule="")},removeCallbackUrlRule(e){const t=this.formData.lnbits_callback_url_rules;this.formData.lnbits_callback_url_rules=t.filter(t=>t!==e)},addNostrUrl(){const e=this.nostrAcceptedUrl.trim();this.removeNostrUrl(e),this.formData.nostr_absolute_request_urls.push(e),this.nostrAcceptedUrl=""},removeNostrUrl(e){this.formData.nostr_absolute_request_urls=this.formData.nostr_absolute_request_urls.filter(t=>t!==e)},async toggleServerLog(){if(this.serverlogEnabled=!this.serverlogEnabled,this.serverlogEnabled){const e="http:"!==location.protocol?"wss://":"ws://",t=await LNbits.utils.digestMessage(this.g.user.id),s=e+document.domain+":"+location.port+"/api/v1/ws/"+t;this.ws=new WebSocket(s),this.ws.addEventListener("message",async({data:e})=>{this.logs.push(e.toString());const t=this.$refs.logScroll;if(t){const e=t.getScrollTarget(),s=0;t.setScrollPosition(e.scrollHeight,s)}})}else this.ws.close()}}}),window.app.component("lnbits-admin-users",{props:["form-data"],template:"#lnbits-admin-users",data:()=>({formAddUser:"",formAddAdmin:"",formAddActivationCode:"",showReusableActivationCode:!1}),methods:{addAllowedUser(){let e=this.formAddUser,t=this.formData.lnbits_allowed_users;e&&e.length&&!t.includes(e)&&(this.formData.lnbits_allowed_users=[...t,e],this.formAddUser="")},removeAllowedUser(e){let t=this.formData.lnbits_allowed_users;this.formData.lnbits_allowed_users=t.filter(t=>t!==e)},addAdminUser(){let e=this.formAddAdmin,t=this.formData.lnbits_admin_users;e&&e.length&&!t.includes(e)&&(this.formData.lnbits_admin_users=[...t,e],this.formAddAdmin="")},removeAdminUser(e){let t=this.formData.lnbits_admin_users;this.formData.lnbits_admin_users=t.filter(t=>t!==e)},addOneTimeActivationCode(){const e=this.formAddActivationCode,t=this.formData.lnbits_register_one_time_activation_codes;e?.length&&!t.includes(e)&&(this.formData.lnbits_register_one_time_activation_codes=[...t,e],this.formAddActivationCode="")},removeOneTimeActivationCode(e){const t=this.formData.lnbits_register_one_time_activation_codes;this.formData.lnbits_register_one_time_activation_codes=t.filter(t=>t!==e)}}}),window.app.component("lnbits-admin-server",{props:["form-data"],template:"#lnbits-admin-server"}),window.app.component("lnbits-admin-extensions",{props:["form-data"],template:"#lnbits-admin-extensions",data:()=>({formAddExtensionsManifest:""}),methods:{addExtensionsManifest(){const e=this.formAddExtensionsManifest.trim(),t=this.formData.lnbits_extensions_manifests;e&&e.length&&!t.includes(e)&&(this.formData.lnbits_extensions_manifests=[...t,e],this.formAddExtensionsManifest="")},removeExtensionsManifest(e){const t=this.formData.lnbits_extensions_manifests;this.formData.lnbits_extensions_manifests=t.filter(t=>t!==e)}}}),window.app.component("lnbits-admin-wasm-runtime",{props:["form-data"],template:"#lnbits-admin-wasm-runtime",data(){return{wasmRuntimeLoading:!1,wasmHistoryLoading:!1,wasmRuntimeTimer:null,wasmStats:{},wasmCurrentInvocations:[],wasmInvocationHistory:[],wasmStatItems:[{key:"total",label:"Total",icon:"data_usage",color:"primary"},{key:"running",label:"Running",icon:"play_circle",color:"green"},{key:"completed",label:"Completed",icon:"task_alt",color:"teal"},{key:"failed",label:"Failed",icon:"error",color:"red"},{key:"stopped",label:"Stopped",icon:"stop_circle",color:"orange"},{key:"timeout",label:"Timeouts",icon:"timer_off",color:"purple"}],wasmCurrentColumns:[{name:"extension_id",label:"Extension",field:"extension_id",align:"left",sortable:!0},{name:"export_name",label:"Export",field:"export_name",align:"left",sortable:!0},{name:"trigger_type",label:"Trigger",field:"trigger_type",align:"left",sortable:!0},{name:"status",label:"Status",field:"status",align:"left",sortable:!0},{name:"user_id",label:"User",field:"user_id",align:"left",sortable:!0},{name:"started_at",label:"Started",field:"started_at",align:"left",sortable:!0},{name:"duration_ms",label:"Duration",field:e=>e.duration_ms||0,align:"right",sortable:!0},{name:"context",label:"Context",field:e=>this.wasmContextValue(e),align:"left",sortable:!0},{name:"actions",label:"",field:"actions",align:"right"}],wasmHistoryColumns:[{name:"extension_id",label:"Extension",field:"extension_id",align:"left",sortable:!0},{name:"export_name",label:"Export",field:"export_name",align:"left",sortable:!0},{name:"trigger_type",label:"Trigger",field:"trigger_type",align:"left",sortable:!0},{name:"status",label:"Status",field:"status",align:"left",sortable:!0},{name:"user_id",label:"User",field:"user_id",align:"left",sortable:!0},{name:"started_at",label:"Started",field:"started_at",align:"left",sortable:!0},{name:"duration_ms",label:"Duration",field:e=>e.duration_ms||0,align:"right",sortable:!0},{name:"calls",label:"Calls",field:e=>this.wasmCallCount(e),align:"left",sortable:!0},{name:"context",label:"Context",field:e=>this.wasmContextValue(e),align:"left",sortable:!0},{name:"error_message",label:"Error/Stop Reason",field:e=>e.error_message||e.stop_reason||"",align:"left",sortable:!0}]}},computed:{adminKey(){return this.g.user.wallets[0].adminkey},wasmExtensionId(){return this.$route.params.extId||null},wasmExtensionQuery(){return this.wasmExtensionId?`extension_id=${encodeURIComponent(this.wasmExtensionId)}`:""}},watch:{wasmExtensionId(){this.fetchWasmRuntime()}},methods:{async fetchWasmRuntime(){await Promise.all([this.fetchWasmCurrentInvocations(),this.fetchWasmInvocationHistory(),this.fetchWasmInvocationStats()])},async fetchWasmCurrentInvocations(){this.wasmRuntimeLoading=!0;try{const e=this.wasmExtensionQuery?`?${this.wasmExtensionQuery}`:"",{data:t}=await LNbits.api.request("GET",`/api/v1/extension/wasm/invocations/current${e}`,this.adminKey);this.wasmCurrentInvocations=t||[]}catch(e){LNbits.utils.notifyApiError(e)}finally{this.wasmRuntimeLoading=!1}},async fetchWasmInvocationHistory(){this.wasmHistoryLoading=!0;try{const e=["limit=50"];this.wasmExtensionQuery&&e.push(this.wasmExtensionQuery);const{data:t}=await LNbits.api.request("GET",`/api/v1/extension/wasm/invocations?${e.join("&")}`,this.adminKey);this.wasmInvocationHistory=t||[]}catch(e){LNbits.utils.notifyApiError(e)}finally{this.wasmHistoryLoading=!1}},async fetchWasmInvocationStats(){try{const e=["hours=24"];this.wasmExtensionQuery&&e.push(this.wasmExtensionQuery);const{data:t}=await LNbits.api.request("GET",`/api/v1/extension/wasm/invocations/stats?${e.join("&")}`,this.adminKey);this.wasmStats=t||{}}catch(e){LNbits.utils.notifyApiError(e)}},async stopWasmInvocation(e){try{await LNbits.api.request("POST",`/api/v1/extension/wasm/invocations/${encodeURIComponent(e)}/stop`,this.adminKey),Quasar.Notify.create({type:"positive",message:"WASM invocation stop requested."}),await this.fetchWasmRuntime()}catch(e){LNbits.utils.notifyApiError(e)}},deactivateWasmExtension(e){LNbits.utils.confirmDialog(`Deactivate extension '${e}'?`,"Deactivate Extension").onOk(async()=>{try{await LNbits.api.request("PUT",`/api/v1/extension/${encodeURIComponent(e)}/deactivate`,this.adminKey),Quasar.Notify.create({type:"positive",message:`Extension '${e}' deactivated.`}),await this.fetchWasmRuntime()}catch(e){LNbits.utils.notifyApiError(e)}})},formatWasmStat(e){const t=this.wasmStats[e];return null==t?"0":String(t)},formatWasmDate(e){return e?this.utils.formatDate(e):""},wasmStatusColor:e=>({running:"green",stopping:"orange",completed:"teal",failed:"red",stopped:"orange",timeout:"purple",abandoned:"grey"}[e]||"grey"),wasmTriggerColor:e=>({http:"primary",event:"purple"}[e]||"grey"),formatWasmDuration(e){let t=e.duration_ms;return"running"!==e.status&&"stopping"!==e.status||!e.started_at||(t=Date.now()-new Date(e.started_at).getTime()),null==t?"":t>=1e3?`${(t/1e3).toFixed(1)}s`:`${t}ms`},formatWasmCalls:e=>[`host ${e.host_call_count||0}`,`http ${e.http_call_count||0}`,`storage ${e.storage_call_count||0}`,`wallet ${e.wallet_call_count||0}`].join(" / "),wasmCallCount:e=>(e.host_call_count||0)+(e.http_call_count||0)+(e.storage_call_count||0)+(e.wallet_call_count||0),wasmContextValue:e=>[e.method,e.path,e.event_type,e.wallet_id,e.payment_hash].filter(Boolean).join(" "),formatWasmContext:e=>[e.method,e.path,e.event_type,e.wallet_id?`wallet ${e.wallet_id}`:"",e.payment_hash?`payment ${e.payment_hash.slice(0,12)}...`:""].filter(Boolean).join(" | "),formatWasmUserId(e){if(!e)return"-";const t=String(e);return t.length<=12?t:`${t.slice(0,3)}...${t.slice(-3)}`}},created(){this.fetchWasmRuntime(),this.wasmRuntimeTimer=setInterval(()=>{this.fetchWasmCurrentInvocations()},5e3)},unmounted(){this.wasmRuntimeTimer&&clearInterval(this.wasmRuntimeTimer)}}),window.app.component("lnbits-admin-wasm-limit-config",{props:["form-data"],template:"#lnbits-admin-wasm-limit-config",data:()=>({selectedWasmExtensionId:null,wasmExtensionLimitDraft:{},wasmExtensionLimitsSaving:!1,wasmRuntimeLimitExtensions:[],wasmRuntimeLimitExtensionsLoading:!1,wasmLimitInfoDialog:{show:!1,title:"",details:""},wasmRuntimeLimitGroups:[{title:"Execution",fields:[{name:"wasm_runtime_max_execution_ms",label:"Max execution time (ms)",description:"Maximum wall-clock time allowed for one WASM invocation.",details:"This is the elapsed time from starting the invocation until the export returns. When the limit is reached LNbits requests an interrupt and records the invocation as timed out if it cannot finish quickly. Use this to stop long sleeps, slow host calls, and CPU loops that run for too long."},{name:"wasm_runtime_max_fuel",label:"Max fuel",description:"Maximum Wasmtime instruction budget for one invocation.",details:"Fuel is Wasmtime instruction budgeting. It is more deterministic than wall-clock time for CPU-heavy loops because each executed instruction consumes budget. Set this low enough to stop busy loops, but high enough for legitimate extension startup and JSON processing."},{name:"wasm_runtime_max_wasm_stack_bytes",label:"Max WASM stack (bytes)",description:"Maximum stack space for WASM calls and recursion.",details:"This limits stack used by WebAssembly function calls. It protects the server from deep recursion or very large call chains in extension code. If legitimate extensions fail with stack overflow traps, raise this carefully."}]},{title:"Memory and Data Size",fields:[{name:"wasm_runtime_max_memory_bytes",label:"Max memory (bytes)",description:"Maximum WASM linear memory per invocation.",details:"This caps the linear memory visible to the WASM module. It limits memory.grow and can make instantiation fail if the module asks for too much memory up front. This does not include every byte used by the Python process or Wasmtime engine internals."},{name:"wasm_runtime_max_request_bytes",label:"Max request size (bytes)",description:"Maximum serialized input payload accepted before execution.",details:"This caps the serialized payload passed into a WASM export before execution starts. It protects against huge HTTP bodies, oversized event data, and expensive JSON parsing. Requests above this limit should be rejected before invoking the extension."},{name:"wasm_runtime_max_response_bytes",label:"Max response size (bytes)",description:"Maximum serialized response returned by a WASM export.",details:"This caps the JSON or string response returned by the WASM export. It prevents extensions from returning huge responses that consume memory, slow down API calls, or overload the browser. Responses above this limit are treated as invalid."}]},{title:"Wasmtime Objects",fields:[{name:"wasm_runtime_max_table_elements",label:"Max table elements",description:"Maximum total elements allowed in WASM tables.",details:"Tables store references used by WebAssembly, commonly function references. Limiting table elements prevents a module from allocating very large reference tables. Each table element also has host memory overhead."},{name:"wasm_runtime_max_instances",label:"Max instances",description:"Maximum WebAssembly instances allowed inside one store.",details:"This limits how many WebAssembly instances can be created inside one Wasmtime store. LNbits normally needs one instance per invocation, so a low value is expected. Raising it should only be needed if the runtime starts supporting modules that instantiate other modules."},{name:"wasm_runtime_max_tables",label:"Max tables",description:"Maximum WebAssembly tables allowed inside one store.",details:"This limits the number of WebAssembly tables in the store. It is separate from table elements: one setting limits the number of tables, the other limits their total size. Keep this small unless a component model module legitimately needs more tables."},{name:"wasm_runtime_max_memories",label:"Max memories",description:"Maximum WebAssembly linear memories allowed inside one store.",details:"This limits how many separate linear memories a module can create. Most extensions should need only one memory. Keep this small to reduce memory accounting complexity and prevent multi-memory abuse."}]},{title:"Concurrency",fields:[{name:"wasm_runtime_max_concurrent_invocations",label:"Max concurrent invocations",description:"Maximum running WASM invocations across the server.",details:"This is the global cap for running WASM invocations across all extensions and users. It protects the LNbits process from thread exhaustion, CPU pressure, and too many simultaneous stores. New invocations should be rejected or queued once this is reached."},{name:"wasm_runtime_max_concurrent_invocations_per_extension",label:"Max concurrent per extension",description:"Maximum running WASM invocations for one extension.",details:"This caps how many invocations a single extension can run at once. It prevents one malicious or buggy extension from consuming the whole global concurrency budget. Set it lower than the global limit."},{name:"wasm_runtime_max_concurrent_invocations_per_user",label:"Max concurrent per user",description:"Maximum running WASM invocations for one user.",details:"This caps concurrent invocations attributed to one user. It helps protect against a user repeatedly clicking, refreshing, or scripting extension calls. Invocations without a user can still be governed by the global and per-extension limits."}]},{title:"Host Calls",fields:[{name:"wasm_runtime_max_host_calls",label:"Max host calls",description:"Maximum total calls from WASM into LNbits host APIs.",details:"This is the total budget for calls from the WASM module into LNbits host APIs during one invocation. It should count all categories together. It limits chatty extensions and prevents tight loops that repeatedly call back into Python."},{name:"wasm_runtime_max_http_calls",label:"Max HTTP calls",description:"Maximum outbound HTTP host calls per invocation.",details:"This caps outbound HTTP requests made through the host API during one invocation. It reduces SSRF blast radius, protects network resources, and limits slow external dependencies. It should be enforced together with HTTP timeout and response-size limits."},{name:"wasm_runtime_max_storage_calls",label:"Max storage calls",description:"Maximum storage host calls per invocation.",details:"This caps extension storage operations during one invocation. It protects the database from excessive reads and writes triggered by malicious loops. Use it with storage payload-size limits if those are added later."},{name:"wasm_runtime_max_wallet_calls",label:"Max wallet calls",description:"Maximum wallet/payment host calls per invocation.",details:"This caps wallet and payment-related host calls during one invocation. These calls are security-sensitive and may touch balances, invoices, or payments. Keep this conservative and rely on explicit permissions for what the extension is allowed to do."}]},{title:"HTTP",fields:[{name:"wasm_runtime_http_timeout_ms",label:"HTTP timeout (ms)",description:"Maximum time allowed for one WASM HTTP request.",details:"This is the per-request timeout for HTTP calls made through the WASM host API. It prevents a slow remote server from holding an invocation open indefinitely. The total invocation timeout still applies across all work."},{name:"wasm_runtime_max_http_response_bytes",label:"Max HTTP response size (bytes)",description:"Maximum response body size accepted from one WASM HTTP request.",details:"This caps the response body accepted from each HTTP call made by an extension. It protects memory and parsing time when a remote server returns a very large body. Responses above the limit should fail the host call."}]}]}),computed:{adminKey(){return this.g.user.wallets[0].adminkey},isExtensionLimitRoute(){return this.$route.path.startsWith("/admin/extensions/wasm/limits/")},routeWasmExtensionId(){return this.isExtensionLimitRoute?this.$route.params.extId:null},backRoute(){return this.isExtensionLimitRoute?"/admin/extensions/wasm/limits":"/admin#extensions"},backTooltip(){return this.isExtensionLimitRoute?"Wasm Limit Config":"Extensions Settings"},pageDescription(){return this.isExtensionLimitRoute?"Customize limits for one installed WASM extension.":"These values are global defaults. Use 0 to disable a global limit."},wasmRuntimeLimitExtensionOptions(){return this.wasmRuntimeLimitExtensions.map(e=>({label:`${e.name||e.id} (${e.id})`,value:e.id}))},selectedWasmRuntimeLimitExtension(){return this.wasmRuntimeLimitExtensions.find(e=>e.id===this.selectedWasmExtensionId)||null},customWasmLimitCount(){const e=this.selectedWasmRuntimeLimitExtension;return e&&e.wasm_runtime_limits?Object.keys(e.wasm_runtime_limits).length:0}},watch:{selectedWasmExtensionId(){this.loadSelectedWasmRuntimeLimitExtension()},routeWasmExtensionId(){this.syncWasmExtensionLimitRoute()}},created(){this.fetchWasmRuntimeLimitExtensions()},methods:{async fetchWasmRuntimeLimitExtensions(){this.wasmRuntimeLimitExtensionsLoading=!0;try{const{data:e}=await LNbits.api.request("GET","/api/v1/extension/wasm/runtime-limits/extensions",this.adminKey);this.wasmRuntimeLimitExtensions=e||[],this.selectedWasmExtensionId&&!this.wasmRuntimeLimitExtensions.some(e=>e.id===this.selectedWasmExtensionId)&&(this.selectedWasmExtensionId=null),this.syncWasmExtensionLimitRoute()}catch(e){LNbits.utils.notifyApiError(e)}finally{this.wasmRuntimeLimitExtensionsLoading=!1}},syncWasmExtensionLimitRoute(){this.selectedWasmExtensionId=this.routeWasmExtensionId,this.loadSelectedWasmRuntimeLimitExtension()},openWasmExtensionLimit(e){this.$router.push(`/admin/extensions/wasm/limits/${encodeURIComponent(e)}`)},loadSelectedWasmRuntimeLimitExtension(){const e=this.selectedWasmRuntimeLimitExtension;this.wasmExtensionLimitDraft=e?{...e.wasm_runtime_limits||{}}:{}},wasmExtensionLimitHint(e){return`Inherited global value: ${this.formData[e.name]}. ${e.description}`},wasmExtensionLimitPlaceholder(e){const t=this.formData[e.name];return null==t?"":String(t)},normalizedWasmExtensionLimitDraft(){const e={};return this.wasmRuntimeLimitGroups.forEach(t=>{t.fields.forEach(t=>{const s=this.wasmExtensionLimitDraft[t.name],a="string"==typeof s?s.trim():s;if(""===a||null==a)return;const i=Number(a);if(!Number.isFinite(i)||!Number.isInteger(i)||i<0)throw new Error(`${t.label} must be a non-negative integer.`);e[t.name]=i})}),e},async clearWasmExtensionLimits(){this.wasmExtensionLimitDraft={},await this.saveWasmExtensionLimits()},async saveWasmExtensionLimits(){if(this.selectedWasmExtensionId){this.wasmExtensionLimitsSaving=!0;try{const{data:e}=await LNbits.api.request("PUT",`/api/v1/extension/wasm/runtime-limits/${encodeURIComponent(this.selectedWasmExtensionId)}`,this.adminKey,{limits:this.normalizedWasmExtensionLimitDraft()}),t=this.wasmRuntimeLimitExtensions.findIndex(t=>t.id===e.id);t>=0&&this.wasmRuntimeLimitExtensions.splice(t,1,e),this.loadSelectedWasmRuntimeLimitExtension(),Quasar.Notify.create({type:"positive",message:"WASM extension limits saved."})}catch(e){e instanceof Error&&!e.response?Quasar.Notify.create({type:"negative",message:e.message}):LNbits.utils.notifyApiError(e)}finally{this.wasmExtensionLimitsSaving=!1}}},showWasmLimitInfo(e){this.wasmLimitInfoDialog={show:!0,title:e.label,details:e.details}}}}),window.app.component("lnbits-admin-notifications",{props:["form-data"],template:"#lnbits-admin-notifications",data:()=>({nostrNotificationIdentifier:"",emailNotificationAddress:""}),methods:{sendTestEmail(){LNbits.api.request("GET","/admin/api/v1/testemail",this.g.user.wallets[0].adminkey).then(e=>{if("error"===e.data.status)throw new Error(e.data.message);this.$q.notify({message:"Test email sent!",color:"positive"})}).catch(e=>{this.$q.notify({message:e.message,color:"negative"})})},addNostrNotificationIdentifier(){const e=this.nostrNotificationIdentifier.trim(),t=this.formData.lnbits_nostr_notifications_identifiers;e&&e.length&&!t.includes(e)&&(this.formData.lnbits_nostr_notifications_identifiers=[...t,e],this.nostrNotificationIdentifier="")},removeNostrNotificationIdentifier(e){const t=this.formData.lnbits_nostr_notifications_identifiers;this.formData.lnbits_nostr_notifications_identifiers=t.filter(t=>t!==e)},addEmailNotificationAddress(){const e=this.emailNotificationAddress.trim(),t=this.formData.lnbits_email_notifications_to_emails;e&&e.length&&!t.includes(e)&&(this.formData.lnbits_email_notifications_to_emails=[...t,e],this.emailNotificationAddress="")},removeEmailNotificationAddress(e){const t=this.formData.lnbits_email_notifications_to_emails;this.formData.lnbits_email_notifications_to_emails=t.filter(t=>t!==e)}}}),window.app.component("lnbits-admin-site-customisation",{props:["form-data"],template:"#lnbits-admin-site-customisation",data:()=>({lnbits_theme_options:["classic","bitcoin","flamingo","cyber","freedom","mint","autumn","monochrome","salvador"],colors:["primary","secondary","accent","positive","negative","info","warning","red","yellow","orange"],reactionOptions:["none","confettiBothSides","confettiFireworks","confettiStars","confettiTop"],globalBorderOptions:["retro-border","hard-border","neon-border","no-border"]}),methods:{onBackgroundImageInput(e){const t=e.target.files[0];t&&this.uploadBackgroundImage(t),e.target.value=null},async uploadBackgroundImage(e){const t=new FormData;t.append("file",e);try{const{data:e}=await LNbits.api.request("POST","/api/v1/assets?public_asset=true",null,t,{headers:{"Content-Type":"multipart/form-data"}}),s=`${window.location.origin}/api/v1/assets/${e.id}/thumbnail`;this.formData.lnbits_default_bgimage=s,Quasar.Notify.create({type:"positive",message:"Background image uploaded.",icon:null})}catch(e){LNbits.utils.notifyApiError(e)}}}}),window.app.component("lnbits-admin-assets-config",{props:["form-data"],template:"#lnbits-admin-assets-config",data:()=>({newAllowedAssetMimeType:"",newNoLimitUser:""}),async created(){},methods:{addAllowedAssetMimeType(){this.newAllowedAssetMimeType&&(this.removeAllowedAssetMimeType(this.newAllowedAssetMimeType),this.formData.lnbits_assets_allowed_mime_types.push(this.newAllowedAssetMimeType),this.newAllowedAssetMimeType="",this.formData.touch=null)},removeAllowedAssetMimeType(e){const t=this.formData.lnbits_assets_allowed_mime_types.indexOf(e);-1!==t&&this.formData.lnbits_assets_allowed_mime_types.splice(t,1),this.formData.touch=null},addNewNoLimitUser(){this.newNoLimitUser&&(this.removeNoLimitUser(this.newNoLimitUser),this.formData.lnbits_assets_no_limit_users.push(this.newNoLimitUser),this.newNoLimitUser="",this.formData.touch=null)},removeNoLimitUser(e){e&&(this.formData.lnbits_assets_no_limit_users=this.formData.lnbits_assets_no_limit_users.filter(t=>t!==e),this.formData.touch=null)}}}),window.app.component("lnbits-admin-audit",{props:["form-data"],template:"#lnbits-admin-audit",data:()=>({formAddIncludePath:"",formAddExcludePath:"",formAddIncludeResponseCode:""}),methods:{addIncludePath(){if(""===this.formAddIncludePath)return;const e=this.formData.lnbits_audit_include_paths;e.includes(this.formAddIncludePath)||(this.formData.lnbits_audit_include_paths=[...e,this.formAddIncludePath]),this.formAddIncludePath=""},removeIncludePath(e){this.formData.lnbits_audit_include_paths=this.formData.lnbits_audit_include_paths.filter(t=>t!==e)},addExcludePath(){if(""===this.formAddExcludePath)return;const e=this.formData.lnbits_audit_exclude_paths;e.includes(this.formAddExcludePath)||(this.formData.lnbits_audit_exclude_paths=[...e,this.formAddExcludePath]),this.formAddExcludePath=""},removeExcludePath(e){this.formData.lnbits_audit_exclude_paths=this.formData.lnbits_audit_exclude_paths.filter(t=>t!==e)},addIncludeResponseCode(){if(""===this.formAddIncludeResponseCode)return;const e=this.formData.lnbits_audit_http_response_codes;e.includes(this.formAddIncludeResponseCode)||(this.formData.lnbits_audit_http_response_codes=[...e,this.formAddIncludeResponseCode]),this.formAddIncludeResponseCode=""},removeIncludeResponseCode(e){this.formData.lnbits_audit_http_response_codes=this.formData.lnbits_audit_http_response_codes.filter(t=>t!==e)}}}),window.app.component("lnbits-wallet-charts",{template:"#lnbits-wallet-charts",props:["paymentFilter","chartConfig"],data:()=>({debounceTimeoutValue:1337,debounceTimeout:null,chartData:[],chartDataPointCount:0,walletBalanceChart:null,walletBalanceInOut:null,walletPaymentInOut:null,colorPrimary:Quasar.colors.changeAlpha(Quasar.colors.getPaletteColor("primary"),.3),colorSecondary:Quasar.colors.changeAlpha(Quasar.colors.getPaletteColor("secondary"),.3),barOptions:{responsive:!0,maintainAspectRatio:!1,scales:{x:{stacked:!0},y:{stacked:!0}}}}),watch:{paymentFilter:{deep:!0,handler(){this.changeCharts()}},chartConfig:{deep:!0,handler(e){this.$q.localStorage.setItem("lnbits.wallets.chartConfig",e),this.changeCharts()}}},methods:{changeCharts(){this.debounceTimeout&&clearTimeout(this.debounceTimeout),this.debounceTimeout=setTimeout(async()=>{await this.fetchChartData(),this.drawCharts()},this.debounceTimeoutValue)},filterChartData(){const e=this.paymentFilter["time[ge]"]+"T00:00:00",t=this.paymentFilter["time[le]"]+"T23:59:59";let s=0,a=this.chartData.map(e=>void 0!==this.paymentFilter["amount[ge]"]?(s+=e.balance_in,{...e,balance:s,balance_out:0,count_out:0}):void 0!==this.paymentFilter["amount[le]"]?(s-=e.balance_out,{...e,balance:s,balance_in:0,count_in:0}):{...e});a=a.filter(s=>this.paymentFilter["time[ge]"]&&this.paymentFilter["time[le]"]?s.date>=e&&s.date<=t:this.paymentFilter["time[ge]"]?s.date>=e:!this.paymentFilter["time[le]"]||s.date<=t);const i=a.map(e=>new Date(e.date).toLocaleString("default",{month:"short",day:"numeric"}));return this.chartDataPointCount=a.length,{data:a,labels:i}},drawBalanceInOutChart(e,t){this.walletBalanceInOut&&this.walletBalanceInOut.destroy();const s=this.$refs.walletBalanceInOut;s&&(this.walletBalanceInOut=new Chart(s.getContext("2d"),{type:"bar",options:this.barOptions,data:{labels:t,datasets:[{label:"Balance In",borderRadius:5,data:e.map(e=>e.balance_in),backgroundColor:this.colorPrimary},{label:"Balance Out",borderRadius:5,data:e.map(e=>e.balance_out),backgroundColor:this.colorSecondary}]}}))},drawPaymentInOut(e,t){this.walletPaymentInOut&&this.walletPaymentInOut.destroy();const s=this.$refs.walletPaymentInOut;s&&(this.walletPaymentInOut=new Chart(s.getContext("2d"),{type:"bar",options:this.barOptions,data:{labels:t,datasets:[{label:"Payments In",data:e.map(e=>e.count_in),backgroundColor:this.colorPrimary},{label:"Payments Out",data:e.map(e=>-e.count_out),backgroundColor:this.colorSecondary}]}}))},drawBalanceChart(e,t){this.walletBalanceChart&&this.walletBalanceChart.destroy();const s=this.$refs.walletBalanceChart;s&&(this.walletBalanceChart=new Chart(s.getContext("2d"),{type:"line",options:{responsive:!0,maintainAspectRatio:!1},data:{labels:t,datasets:[{label:"Balance",data:e.map(e=>e.balance),pointStyle:!1,backgroundColor:this.colorPrimary,borderColor:this.colorPrimary,borderWidth:2,fill:!0,tension:.7,fill:1},{label:"Fees",data:e.map(e=>e.fee),pointStyle:!1,backgroundColor:this.colorSecondary,borderColor:this.colorSecondary,borderWidth:1,fill:!0,tension:.7,fill:1}]}}))},drawCharts(){const{data:e,labels:t}=this.filterChartData();this.chartConfig.showBalanceChart&&this.drawBalanceChart(e,t),this.chartConfig.showBalanceInOutChart&&this.drawBalanceInOutChart(e,t),this.chartConfig.showPaymentInOutChart&&this.drawPaymentInOut(e,t)},async fetchChartData(){try{const{data:e}=await LNbits.api.request("GET",`/api/v1/payments/stats/daily?wallet_id=${this.g.wallet.id}`);this.chartData=e}catch(e){console.warn(e),LNbits.utils.notifyApiError(e)}}},async created(){await this.fetchChartData(),this.drawCharts()}}),window.app.component("lnbits-wallet-api-docs",{template:"#lnbits-wallet-api-docs",methods:{copyAdminKey(){LNbits.utils.confirmDialog(this.$t("admin_key_warning")).onOk(()=>LNbits.utils.copyText(this.g.wallet.adminkey))},resetKeys(){LNbits.utils.confirmDialog("Are you sure you want to reset your API keys?").onOk(()=>{LNbits.api.resetWalletKeys(this.g.wallet).then(e=>{const{id:t,adminkey:s,inkey:a}=e;this.g.wallet={...this.g.wallet,inkey:a,adminkey:s};const i=this.g.user.wallets.findIndex(e=>e.id===t);-1!==i&&(this.g.user.wallets[i]={...this.g.user.wallets[i],inkey:a,adminkey:s}),Quasar.Notify.create({timeout:3500,type:"positive",message:"API keys reset!"})}).catch(e=>{LNbits.utils.notifyApiError(e)})})}},data:()=>({origin:window.location.origin,inkeyHidden:!0,adminkeyHidden:!0,walletIdHidden:!0})}),window.app.component("lnbits-wallet-icon",{template:"#lnbits-wallet-icon",data:()=>({icon:{show:!1,data:{},colorOptions:["primary","purple","orange","green","brown","blue","red","pink"],options:["home","star","bolt","paid","savings","store","videocam","music_note","flight","train","directions_car","school","construction","science","sports_esports","sports_tennis","theaters","water","headset_mic","videogame_asset","person","group","pets","sunny","elderly","verified","snooze","mail","forum","shopping_cart","shopping_bag","attach_money","print_connect","dark_mode","light_mode","android","network_wifi","shield","fitness_center","lunch_dining"]}}),methods:{setSelectedIcon(e){this.icon.data.icon=e},setSelectedColor(e){this.icon.data.color=e},setIcon(){this.$emit("update-wallet",this.icon.data),this.icon.show=!1}}}),window.app.component("lnbits-wallet-new",{template:"#lnbits-wallet-new",data:()=>({walletTypes:[{label:"Lightning Wallet",value:"lightning"}],wallet:{name:"",sharedWalletId:""},showNewWalletDialog:!1}),watch:{"g.newWalletType"(e){null!==e&&(this.showNewWalletDialog=!0)},showNewWalletDialog(e){!0!==e&&this.reset()}},computed:{isLightning(){return"lightning"===this.g.newWalletType},isLightningShared(){return"lightning-shared"===this.g.newWalletType},inviteWalletOptions(){return(this.g.user?.extra?.wallet_invite_requests||[]).map(e=>({label:`${e.to_wallet_name} (from ${e.from_user_name})`,value:e.to_wallet_id}))}},methods:{reset(){this.showNewWalletDialog=!1,this.g.newWalletType=null,this.wallet={name:"",sharedWalletId:""}},async submitRejectWalletInvitation(){try{const e=this.g.user.extra.wallet_invite_requests||[],t=e.find(e=>e.to_wallet_id===this.wallet.sharedWalletId);if(!t)return void Quasar.Notify.create({message:"Cannot find invitation for the selected wallet.",type:"warning"});await LNbits.api.request("DELETE",`/api/v1/wallet/share/invite/${t.request_id}`,this.g.wallet.adminkey),Quasar.Notify.create({message:"Invitation rejected.",type:"positive"}),this.g.user.extra.wallet_invite_requests=e.filter(e=>e.request_id!==t.request_id)}catch(e){LNbits.utils.notifyApiError(e)}},submitAddWallet(){const e=this.wallet;"lightning"!==this.g.newWalletType||e.name?"lightning-shared"!==this.g.newWalletType||e.sharedWalletId?LNbits.api.createWallet(e.name,this.g.newWalletType,{shared_wallet_id:e.sharedWalletId}).then(e=>{this.$q.notify({message:"Wallet created successfully",color:"positive"}),this.reset(),this.g.user.wallets.push(LNbits.map.wallet(e.data)),this.g.lastWalletId=e.data.id,this.$router.push(`/wallet/${e.data.id}`)}).catch(LNbits.utils.notifyApiError):this.$q.notify({message:"Missing a shared wallet ID",color:"warning"}):this.$q.notify({message:"Please enter a name for the wallet",color:"warning"})}},created(){this.g.user?.extra?.wallet_invite_requests?.length&&this.walletTypes.push({label:`Lightning Wallet (Share Invite: ${this.g.user.extra.wallet_invite_requests.length})`,value:"lightning-shared"})}}),window.app.component("lnbits-wallet-share",{template:"#lnbits-wallet-share",computed:{walletApprovedShares(){return this.g.wallet.extra.shared_with.filter(e=>"approved"===e.status)},walletPendingRequests(){return this.g.wallet.extra.shared_with.filter(e=>"request_access"===e.status)},walletPendingInvites(){return this.g.wallet.extra.shared_with.filter(e=>"invite_sent"===e.status)}},data:()=>({permissionOptions:[{label:"View",value:"view-payments"},{label:"Receive",value:"receive-payments"},{label:"Send",value:"send-payments"}],walletShareInvite:{username:"",permissions:[]}}),methods:{async updateSharePermissions(e){try{const{data:t}=await LNbits.api.request("PUT","/api/v1/wallet/share",this.g.wallet.adminkey,e);Object.assign(e,t),Quasar.Notify.create({message:"Wallet permission updated.",type:"positive"})}catch(e){LNbits.utils.notifyApiError(e)}},async inviteUserToWallet(){try{const{data:e}=await LNbits.api.request("PUT","/api/v1/wallet/share/invite",this.g.wallet.adminkey,{...this.walletShareInvite,status:"invite_sent",wallet_id:this.g.wallet.id});this.g.wallet.extra.shared_with.push(e),this.walletShareInvite={username:"",permissions:[]},Quasar.Notify.create({message:"User invited to wallet.",type:"positive"})}catch(e){LNbits.utils.notifyApiError(e)}},deleteSharePermission(e){LNbits.utils.confirmDialog("Are you sure you want to remove this share permission?").onOk(async()=>{try{await LNbits.api.request("DELETE",`/api/v1/wallet/share/${e.request_id}`,this.g.wallet.adminkey),this.g.wallet.extra.shared_with=this.g.wallet.extra.shared_with.filter(t=>t.wallet_id!==e.wallet_id),Quasar.Notify.create({message:"Wallet permission deleted.",type:"positive"})}catch(e){LNbits.utils.notifyApiError(e)}})}}}),window.app.component("lnbits-wallet-paylinks",{template:"#lnbits-wallet-paylinks",data:()=>({storedPaylinks:[]}),watch:{"g.wallet"(e){this.storedPaylinks=e.storedPaylinks??[]}},created(){this.storedPaylinks=this.g.wallet.storedPaylinks},methods:{updatePaylinks(){LNbits.api.request("PUT",`/api/v1/wallet/stored_paylinks/${this.g.wallet.id}`,this.g.wallet.adminkey,{links:this.storedPaylinks}).then(()=>{this.$q.notify({message:"Paylinks updated.",type:"positive",timeout:3500})}).catch(e=>{LNbits.utils.notifyApiError(e)})},sendToPaylink(e){this.$emit("send-lnurl",e)},editPaylink(){this.$nextTick(()=>{this.updatePaylinks()})},deletePaylink(e){const t=[];this.storedPaylinks.forEach(s=>{s.lnurl!==e&&t.push(s)}),this.storedPaylinks=t,this.updatePaylinks()}}}),window.app.component("lnbits-wallet-extra",{template:"#lnbits-wallet-extra",props:["chartConfig"],computed:{exportUrl(){return`${window.location.origin}/wallet?usr=${this.g.user.id}&wal=${this.g.wallet.id}`}},methods:{handleSendLnurl(e){this.$emit("send-lnurl",e)},updateWallet(e){this.$emit("update-wallet",e)},handleFiatTracking(){this.g.fiatTracking=!this.g.fiatTracking,this.g.fiatTracking?(this.updateWallet({currency:this.g.wallet.currency}),this.updateFiatBalance()):(this.g.isFiatPriority=!1,this.g.wallet.currency="",this.updateWallet({currency:""}))},deleteWallet(){LNbits.utils.confirmDialog("Are you sure you want to delete this wallet?").onOk(()=>{LNbits.api.deleteWallet(this.g.wallet).then(()=>{this.g.user.wallets=this.g.user.wallets.filter(e=>e.id!==this.g.wallet.id),this.g.lastActiveWallet=this.g.user.wallets[0].id,this.$router.push(`/wallet/${this.g.lastActiveWallet}`),Quasar.Notify.create({timeout:3e3,message:"Wallet deleted!",spinner:!0})}).catch(e=>{LNbits.utils.notifyApiError(e)})})},updateFiatBalance(){this.$q.localStorage.getItem("lnbits.exchangeRate."+this.g.wallet.currency)&&(this.g.exchangeRate=this.$q.localStorage.getItem("lnbits.exchangeRate."+this.g.wallet.currency),this.g.fiatBalance=this.g.exchangeRate/1e8*this.g.wallet.sat),LNbits.api.request("GET","/api/v1/rate/"+this.g.wallet.currency,null).then(e=>{this.g.fiatBalance=e.data.price/1e8*this.g.wallet.sat,this.g.exchangeRate=e.data.price.toFixed(2),this.g.fiatTracking=!0,this.$q.localStorage.set("lnbits.exchangeRate."+this.g.wallet.currency,this.g.exchangeRate),this.g.exchangeRate<=0&&(this.g.fiatTracking=!1,this.g.isFiatPriority=!1)}).catch(e=>console.error(e))}},created(){""!==this.g.wallet.currency&&this.g.isSatsDenomination?(this.g.fiatTracking=!0,this.updateFiatBalance()):this.g.fiatTracking=!1}}),window.app.component("lnbits-home-logos",{template:"#lnbits-home-logos",data:()=>({logos:[{href:"https://github.com/ElementsProject/lightning",lightSrc:"/static/images/clnl.png",darkSrc:"/static/images/cln.png"},{href:"https://github.com/lightningnetwork/lnd",lightSrc:"/static/images/lnd.png",darkSrc:"/static/images/lnd.png"},{href:"https://opennode.com",lightSrc:"/static/images/opennodel.png",darkSrc:"/static/images/opennode.png"},{href:"https://lnpay.co/",lightSrc:"/static/images/lnpayl.png",darkSrc:"/static/images/lnpay.png"},{href:"https://github.com/rootzoll/raspiblitz",lightSrc:"/static/images/blitzl.png",darkSrc:"/static/images/blitz.png"},{href:"https://start9.com/",lightSrc:"/static/images/start9l.png",darkSrc:"/static/images/start9.png"},{href:"https://getumbrel.com/",lightSrc:"/static/images/umbrell.png",darkSrc:"/static/images/umbrel.png"},{href:"https://mynodebtc.com",lightSrc:"/static/images/mynodel.png",darkSrc:"/static/images/mynode.png"},{href:"https://github.com/shesek/spark-wallet",lightSrc:"/static/images/sparkl.png",darkSrc:"/static/images/spark.png"},{href:"https://voltage.cloud",lightSrc:"/static/images/voltagel.png",darkSrc:"/static/images/voltage.png"},{href:"https://breez.technology/sdk/",lightSrc:"/static/images/breezl.png",darkSrc:"/static/images/breez.png"},{href:"https://blockstream.com/lightning/greenlight/",lightSrc:"/static/images/greenlightl.png",darkSrc:"/static/images/greenlight.png"},{href:"https://getalby.com",lightSrc:"/static/images/albyl.png",darkSrc:"/static/images/alby.png"},{href:"https://zbd.gg",lightSrc:"/static/images/zbdl.png",darkSrc:"/static/images/zbd.png"},{href:"https://phoenix.acinq.co/server",lightSrc:"/static/images/phoenixdl.png",darkSrc:"/static/images/phoenixd.png"},{href:"https://boltz.exchange/",lightSrc:"/static/images/boltzl.svg",darkSrc:"/static/images/boltz.svg"},{href:"https://www.blink.sv/",lightSrc:"/static/images/blink_logol.png",darkSrc:"/static/images/blink_logo.png"}]}),computed:{showLogos(){return this.g.isSatsDenomination&&"LNbits"==this.g.settings.siteTitle&&1==this.g.settings.showHomePageElements}}}),window.app.component("lnbits-error",{template:"#lnbits-error",props:["dynamic","code","message"],computed:{isExtension(){return 403==this.code&&(!!this.message.startsWith("Extension ")||void 0)}},methods:{goBack(){window.history.back()},goHome(){window.location="/"},goToWallet(){this.dynamic?this.$router.push("/wallet"):window.location="/wallet"},goToExtension(){const e=`/extensions#${this.message.match(/'([^']+)'/)[1]}`;this.dynamic?this.$router.push(e):window.location=e},async logOut(){try{await LNbits.api.logout(),window.location="/"}catch(e){LNbits.utils.notifyApiError(e)}}},async created(){if(!this.dynamic&&401==this.code)return console.warn(`Unauthorized: ${this.errorMessage}`),void this.logOut()}}),window.app.component("lnbits-qrcode",{template:"#lnbits-qrcode",components:{QrcodeVue:QrcodeVue.default},props:{value:{type:String,required:!0},nfc:{type:Boolean,default:!1},print:{type:Boolean,default:!1},showButtons:{type:Boolean,default:!0},href:{type:String,default:""},margin:{type:Number,default:3},maxWidth:{type:Number,default:450},logo:{type:String,default:window.g.settings.qrLogo||null}},data:()=>({nfcTagWriting:!1,nfcSupported:"undefined"!=typeof NDEFReader}),methods:{printQrCode(){const e=this.$refs.qrCode.$el.outerHTML,t=window.open("","_blank");t.document.write(`\n \n \n Print QR Code\n \n \n ${e}\n \n `),t.document.close(),t.focus(),t.print(),t.close()},clickQrCode(e){if(""===this.href)return this.utils.copyText(this.value),e.preventDefault(),e.stopPropagation(),!1;this.href&&this.href.startsWith("http")&&(window.open(this.href,"_blank"),e.preventDefault())},async writeNfcTag(){try{if(!this.nfcSupported)throw{toString:function(){return"NFC not supported on this device or browser."}};const e=new NDEFReader;this.nfcTagWriting=!0,this.$q.notify({message:"Tap your NFC tag to write the LNURL-withdraw link to it."}),await e.write({records:[{recordType:"url",data:this.value,lang:"en"}]}),this.nfcTagWriting=!1,this.$q.notify({type:"positive",message:"NFC tag written successfully."})}catch(e){this.nfcTagWriting=!1,this.$q.notify({type:"negative",message:e?e.toString():"An unexpected error has occurred."})}},downloadSVG(){const e=this.$refs.qrCode.$el;if(!e)return void console.error("SVG element not found");let t=(new XMLSerializer).serializeToString(e);t.match(/^]+xmlns="http:\/\/www\.w3\.org\/2000\/svg"/)||(t=t.replace(/^({tab:"bech32",lnurl:""}),methods:{setLnurl(){if("bech32"==this.tab){const e=(new TextEncoder).encode(this.url),t=NostrTools.nip19.encodeBytes("lnurl",e);this.lnurl=this.href&&""!==this.href.trim()?`${this.href}?lightning=${t.toUpperCase()}`:`lightning:${t.toUpperCase()}`}else"lud17"==this.tab&&(this.url.startsWith("http://")?this.lnurl=this.url.replace("http://",this.prefix+"://"):this.lnurl=this.url.replace("https://",this.prefix+"://"));this.$emit("update:lnurl",this.lnurl)}},watch:{url(){this.setLnurl()},tab(){this.setLnurl()}},created(){this.setLnurl()}}),window.app.component("lnbits-disclaimer",{template:"#lnbits-disclaimer",computed:{showDisclaimer:()=>!g.disclaimerShown&&g.isUserAuthorized}}),window.app.component("lnbits-footer",{template:"#lnbits-footer",computed:{title(){return`${this.g.settings.siteTitle}, ${this.g.settings.siteTagline}`},version(){return this.$t("lnbits_version")+": "+this.g.settings.version}}}),window.app.component("lnbits-header",{template:"#lnbits-header",computed:{showAdmin(){return this.g.user&&(this.g.user.super_user||this.g.user.admin)},displayName(){return this.g.user?.extra?.display_name||this.g.user.username||this.g.user?.extra?.first_name||"Anon"},displayRole(){return this.g.user?.super_user?"Super User":this.g.user?.admin?"Admin":"User"}},methods:{async stopImpersonation(){try{await LNbits.api.stopImpersonation(),LNbits.utils.restoreLocalStorage("impersonation"),window.location="/users"}catch(e){console.warn(e)}},async handleLanguageChanged(e){try{await LNbits.api.updateUiCustomization({locale:e.locale}),this.$q.notify({type:"positive",message:"Language Updated",caption:e.locale})}catch(e){LNbits.utils.notifyApiError(e)}}}}),window.app.component("lnbits-header-wallets",{template:"#lnbits-header-wallets"}),window.app.component("lnbits-drawer",{template:"#lnbits-drawer"}),window.app.component("lnbits-theme",{watch:{"g.walletFlip"(e){this.$q.localStorage.setItem("lnbits.walletFlip",e),!0===e&&this.$q.screen.lt.md&&(this.g.visibleDrawer=!1)},"g.disclaimerShown"(e){this.$q.localStorage.setItem("lnbits.disclaimerShown",e)},"g.locale"(e){this.$q.localStorage.setItem("lnbits.lang",e),window.i18n.global.locale=e},"g.isFiatPriority"(e){this.$q.localStorage.setItem("lnbits.isFiatPriority",e)},"g.reactionChoice"(e){this.$q.localStorage.set("lnbits.reactions",e)},"g.themeChoice"(e){document.body.setAttribute("data-theme",e),this.$q.localStorage.set("lnbits.theme",e)},"g.darkChoice"(e){this.$q.dark.set(e),this.$q.localStorage.set("lnbits.darkMode",e),Chart.defaults.color=this.$q.dark.isActive?"#fff":"#000"},"g.borderChoice"(e){document.body.classList.forEach(e=>{e.endsWith("-border")&&document.body.classList.remove(e)}),this.$q.localStorage.setItem("lnbits.border",e),document.body.classList.add(e)},"g.gradientChoice"(e){this.$q.localStorage.set("lnbits.gradientBg",e),!0===e?document.body.classList.add("gradient-bg"):document.body.classList.remove("gradient-bg")},"g.cardRoundedChoice"(e){this.$q.localStorage.set("lnbits.cardRounded",e),!0===e?document.body.classList.add("rounded-ui"):document.body.classList.remove("rounded-ui")},"g.cardGradientChoice"(e){this.$q.localStorage.set("lnbits.cardGradient",e),!0===e?document.body.classList.add("card-gradient"):document.body.classList.remove("card-gradient")},"g.cardShadowChoice"(e){this.$q.localStorage.set("lnbits.cardShadow",e),!0===e?document.body.classList.add("card-shadow"):document.body.classList.remove("card-shadow")},"g.burgerMenuChoice"(e){this.$q.localStorage.set("lnbits.burgerMenu",e),!0===e?document.body.classList.remove("no-burger-background"):document.body.classList.add("no-burger-background")},"g.mobileSimple"(e){this.$q.localStorage.set("lnbits.mobileSimple",e),!0===e?document.body.classList.add("mobile-simple"):document.body.classList.remove("mobile-simple")},"g.bgimageChoice"(e){this.$q.localStorage.set("lnbits.backgroundImage",e),""===e?document.body.classList.remove("bg-image"):(document.body.classList.add("bg-image"),document.body.style.setProperty("--background",`url(${e})`))}},methods:{async checkUrlParams(){const e=new URLSearchParams(window.location.search);if(0===e.length)return;if(e.has("theme")){const t=e.get("theme").trim().toLowerCase();this.g.themeChoice=t,e.delete("theme")}if(e.has("border")){const t=e.get("border").trim().toLowerCase();this.g.borderChoice=t,e.delete("border")}if(e.has("gradient")){const t=e.get("gradient").toLowerCase();this.g.gradientChoice="1"===t||"true"===t,e.delete("gradient")}if(e.has("dark")){const t=e.get("dark").trim().toLowerCase();this.g.darkChoice="1"===t||"true"===t,e.delete("dark")}if(e.has("usr")){try{await LNbits.api.loginUsr(e.get("usr")),window.location.href="/wallet"}catch(e){LNbits.utils.notifyApiError(e)}e.delete("usr")}const t=e.size?`?${e.toString()}`:"",s=window.location.pathname+t;window.history.replaceState(null,null,s)}},created(){this.$q.dark.set(this.g.darkChoice),document.body.setAttribute("data-theme",this.g.themeChoice),Chart.defaults.color=this.$q.dark.isActive?"#fff":"#000",document.body.classList.add(this.g.borderChoice),!0===this.g.gradientChoice&&document.body.classList.add("gradient-bg"),!0===this.g.cardRoundedChoice&&document.body.classList.add("rounded-ui"),!0===this.g.cardGradientChoice&&document.body.classList.add("card-gradient"),!0===this.g.cardShadowChoice&&document.body.classList.add("card-shadow"),!0!==this.g.burgerMenuChoice&&document.body.classList.add("no-burger-background"),""!==this.g.bgimageChoice&&(document.body.classList.add("bg-image"),document.body.style.setProperty("--background",`url(${this.g.bgimageChoice})`)),!0===this.g.mobileSimple&&document.body.classList.add("mobile-simple"),Object.entries(this.g.user?.uiCustomization||{}).forEach(([e,t])=>{e in this.g&&(this.g[e]=t)}),this.checkUrlParams()}}),window.app.component("lnbits-qrcode-scanner",{template:"#lnbits-qrcode-scanner",props:["callback"],data:()=>({showScanner:!1}),watch:{callback(e){return null===e?this.reset():"function"!=typeof e?(Quasar.Notify.create({message:"QR code scanner callback is not a function.",type:"negative"}),this.reset()):!1===this.g.hasCamera?(Quasar.Notify.create({message:"No camera found on this device.",type:"negative"}),this.reset()):void(this.showScanner=!0)}},methods:{reset(){this.showScanner=!1,this.g.scanner=null},detect(e){const t=e[0].rawValue;console.log("Detected QR code value:",t),this.callback(t),this.$emit("detect",t),this.reset()},async onInitQR(e){try{await e}catch(e){const t={NotAllowedError:"ERROR: you need to grant camera access permission",NotFoundError:"ERROR: no camera on this device",NotSupportedError:"ERROR: secure context required (HTTPS, localhost)",NotReadableError:"ERROR: is the camera already in use?",OverconstrainedError:"ERROR: installed cameras are not suitable",StreamApiNotSupportedError:"ERROR: Stream API is not supported in this browser",InsecureContextError:"ERROR: Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP."},s=Object.keys(t).filter(t=>e.name===t),a=s?t[s]:`ERROR: Camera error (${e.name})`;Quasar.Notify.create({message:a,type:"negative"}),this.g.hasCamera=!1,this.reset()}}}}),window.app.component("lnbits-manage-extension-list",{template:"#lnbits-manage-extension-list",data:()=>({extensions:[],userExtensions:[],searchTerm:""}),watch:{"g.user.extensions"(){this.loadExtensions()},searchTerm(){this.filterUserExtensionsByTerm()}},methods:{async loadExtensions(){try{res=await LNbits.api.request("GET","/api/v1/extension"),this.extensions=res.data.sort((e,t)=>e.name.localeCompare(t.name)),this.filterUserExtensionsByTerm()}catch(e){LNbits.utils.notifyApiError(e)}},filterUserExtensionsByTerm(){const e=this.g.user.extensions;this.userExtensions=this.extensions.filter(t=>e.includes(t.code)).filter(e=>""===this.searchTerm||`${e.code} ${e.name} ${e.short_description} ${e.url}`.toLocaleLowerCase().includes(this.searchTerm.toLocaleLowerCase()))},extensionUrl:e=>e.is_wasm?`/ext/${e.code}`:`/${e.code}/`,extensionActive(e){const t=e.is_wasm?`/ext/${e.code}`:`/${e.code}`;return this.$route.path.startsWith(t)}},async created(){await this.loadExtensions()}}),function(){function e(e,t){return e?e(t):t}function t(t,s){const a=function(e){return`extension_permission_${e.id.replace(/[^A-Za-z0-9]/g,"_")}`}(t),i=e(s,a);return i===a?t.id:i}function s(t){return{level:"low",color:"grey-6",label:e(t,"extension_permission_risk_low"),warning:""}}function a(t,s){return{level:"medium",color:"warning",label:e(t,"extension_permission_risk_medium"),warning:s?e(t,s):""}}function i(t,s){return{level:"high",color:"negative",label:e(t,"extension_permission_risk_high"),warning:e(t,s)}}function n(e,t){const s=(e||[]).find(e=>e.id===t);return s?.name||t}function o(e,t){const s=e.policies;return Array.isArray(s)?s.map(e=>{const s="string"==typeof e?e:e?.id;if(!s)return null;const a="string"==typeof e?["read"]:Array.isArray(e.access)&&e.access.length?e.access:["read"];return{id:s,name:n(t,s),access:a}}).filter(Boolean):[]}function r(e,t,n){const r=e.map(e=>function(e,t,n){if(["wallet.pay_invoice","wallet.pay_invoice_background"].includes(e.id))return i(n,"wallet.pay_invoice_background"===e.id?"extension_permission_warning_wallet_pay_invoice_background":"extension_permission_warning_wallet_pay_invoice");if("extension.api.request"===e.id)return o(e,t).some(e=>e.access.includes("write"))?i(n,"extension_permission_warning_extension_api_request_write"):a(n);return"http.request"===e.id?a(n):"wallet.payments.watch"===e.id?a(n,"extension_permission_warning_wallet_payments_watch"):["websocket.publish","websocket.subscribe"].includes(e.id)||["wallet.list","wallet.balance.read","wallet.create_invoice_public","ext.storage.append_public","ext.storage.read_public"].includes(e.id)?a(n):s(n)}(e,t,n)),l=r.find(e=>"high"===e.level);return l||(r.find(e=>"medium"===e.level)||s(n))}function l(e){const t=["wallet.pay_invoice","wallet.pay_invoice_background","wallet.payments.watch","wallet.list","wallet.balance.read","extension.api.request","http.request","ui.camera.scan_qr","websocket","websocket.publish","websocket.subscribe","ext.storage.read","ext.storage.write","ext.storage.read_public","ext.storage.append_public","wallet.create_invoice_public","wallet.create_invoice","utils.basic"],s=t.indexOf(e);return-1===s?t.length:s}function c(s,a,i){const n=s[0],l=2===s.length&&s.some(e=>"ext.storage.read"===e.id)&&s.some(e=>"ext.storage.write"===e.id),c=s.every(e=>["websocket.publish","websocket.subscribe"].includes(e.id))&&s.some(e=>"websocket.publish"===e.id)&&s.some(e=>"websocket.subscribe"===e.id),d=s.map(e=>function(e){return"string"==typeof e.description?e.description:""}(e)).filter(Boolean),h={id:l?"ext.storage.read_write":c?"websocket":n.id,label:l?e(i,"extension_permission_ext_storage_read_write"):c?e(i,"extension_permission_websocket"):t(n,i),risk:r(s,a,i),badges:[],descriptions:d,fieldGroups:[],appendPolicies:[],websocketPublishPolicies:[],invoicePolicies:[],extensionAccess:[],httpHosts:[]};"ext.storage.read_public"===n.id&&(h.fieldGroups=function(e){const t=e.policies;return Array.isArray(t)?t.map(e=>{const t="string"==typeof e?e:e?.table_name||"",s="string"!=typeof e&&Array.isArray(e?.public_fields)?e.public_fields.filter(e=>"string"==typeof e&&e):[],a="string"==typeof e||"string"!=typeof e?.source_id_field?"":e.source_id_field;return t?{table:t,fields:s,sourceIdField:a}:null}).filter(Boolean):[]}(n),h.badges=h.fieldGroups.map(e=>({key:e.table,label:e.table}))),"extension.api.request"===n.id&&(h.extensionAccess=o(n,a),h.badges=h.extensionAccess.map(e=>({key:e.id,label:e.name}))),"http.request"===n.id&&(h.httpHosts=function(e){const t=e.policies;return Array.isArray(t)?t.map(e=>"string"==typeof e?e:e?.host||"").filter(e=>"string"==typeof e&&e):[]}(n)),"wallet.create_invoice_public"===n.id&&(h.invoicePolicies=function(e){const t=e.policies;return Array.isArray(t)?t.map(e=>{if(!e||"object"!=typeof e)return null;const t=e.table,s=e.wallet_field;return"string"==typeof t&&t&&"string"==typeof s&&s?{table:t,walletField:s}:null}).filter(Boolean):[]}(n)),"ext.storage.append_public"===n.id&&(h.appendPolicies=function(e){const t=e.policies;return Array.isArray(t)?t.map(e=>{if(!e||"object"!=typeof e)return null;const t=e.table,s=e.source_table,a=e.source_id_field,i=Array.isArray(e.allowed_fields)?e.allowed_fields.filter(e=>"string"==typeof e&&e):[],n=Number.isInteger(e.max_rows_per_source)?e.max_rows_per_source:1e4;return"string"==typeof t&&t&&"string"==typeof s&&s&&"string"==typeof a&&a?{table:t,sourceTable:s,sourceIdField:a,allowedFields:i,maxRowsPerSource:n,rawPolicy:e}:null}).filter(Boolean):[]}(n),h.badges=h.appendPolicies.map(e=>({key:e.table+":"+e.sourceTable+":"+e.sourceIdField,label:e.table})));const m=s.find(e=>"websocket.publish"===e.id);return m&&(h.websocketPublishPolicies=function(e){const t=e.policies;return Array.isArray(t)?t.map(e=>e&&"object"==typeof e?{maxMessagesPerSecond:Number.isInteger(e.max_messages_per_second)?e.max_messages_per_second:0,rawPolicy:e}:null).filter(Boolean):[]}(m)),h}function d({permissions:e,extensions:t,translate:s}){const a=e||[],i=new Map(a.map(e=>[e.id,e])),n=i.has("ext.storage.read")&&i.has("ext.storage.write"),o=i.has("websocket.publish")&&i.has("websocket.subscribe");let r=!1,d=!1;return a.map((e,t)=>n&&["ext.storage.read","ext.storage.write"].includes(e.id)?r?null:(r=!0,{index:t,orderId:"ext.storage.read",permissions:[i.get("ext.storage.read"),i.get("ext.storage.write")]}):o&&["websocket.publish","websocket.subscribe"].includes(e.id)?d?null:(d=!0,{index:t,orderId:"websocket",permissions:[i.get("websocket.publish"),i.get("websocket.subscribe")]}):{index:t,orderId:e.id,permissions:[e]}).filter(Boolean).sort((e,t)=>{const s=l(e.orderId),a=l(t.orderId);return s===a?e.index-t.index:s-a}).map(e=>c(e.permissions,t||[],s))}window.LNbitsExtensionPermissions={displayItems:d,hasHighRisk:({permissions:e,extensions:t,translate:s})=>d({permissions:e,extensions:t,translate:s}).some(e=>"high"===e.risk.level)},window.app.component("lnbits-extension-permissions",{template:"#lnbits-extension-permissions",props:{permissions:{type:Array,default:()=>[]},extensions:{type:Array,default:()=>[]},editableAppendPublicLimits:{type:Boolean,default:!1},maxRowsPerSourceLimit:{type:Number,default:1e6},editableWebsocketPublishLimits:{type:Boolean,default:!1},maxMessagesPerSecondLimit:{type:Number,default:100}},computed:{displayItems(){return window.LNbitsExtensionPermissions.displayItems({permissions:this.permissions,extensions:this.extensions,translate:e=>this.$t(e)})}},methods:{publicInvoicePolicySentence:e=>`Invoices will be created using ${e.walletField} from ${e.table}.`,publicAppendPolicySentence:e=>`${e.table} rows can be appended for ${e.sourceTable} using ${e.sourceIdField}. Limit: ${e.maxRowsPerSource} rows per source.`,websocketPublishPolicySentence:e=>`Limit: ${e.maxMessagesPerSecond} messages per second.`,permissionAccessLabel(e){const t=`extension_permission_access_${e}`,s=this.$t(t);return s===t?e:s}}})}(),window.app.component("lnbits-manage-wallet-list",{template:"#lnbits-manage-wallet-list",data:()=>({activeWalletId:null}),computed:{maxWallets(){return this.g.user?.extra?.visible_wallet_count||10}},watch:{$route(e){e.path.startsWith("/wallet/")?this.activeWalletId=e.params.id:this.activeWalletId=null},"g.user.wallets":{handler(){this.paymentEvents()},deep:!0,immediate:!0}},created(){this.g.user&&0===this.g.walletEventListeners.length&&this.paymentEvents()},methods:{openNewWalletDialog(){this.g.user.walletInvitesCount?this.g.newWalletType="lightning-shared":this.g.newWalletType="lightning"},onWebsocketMessage(e){const t=JSON.parse(e.data);t.payment?(this.g.user.wallets.forEach(e=>{e.id===t.payment.wallet_id&&(e.sat=t.wallet_balance)}),this.g.wallet.id===t.payment.wallet_id&&(this.g.wallet.sat=t.wallet_balance,this.g.updatePayments=!this.g.updatePayments,this.g.updatePaymentsHash=!this.g.updatePaymentsHash),t.payment.amount>0&&eventReaction(1e3*t.wallet_balance)):console.error("ws message no payment",t)},paymentEvents(){if(!this.g.user)return;let e;this.g.user.wallets.slice(0,this.maxWallets).forEach(t=>{if(!this.g.walletEventListeners.includes(t.id)){this.g.walletEventListeners.push(t.id);const s=new WebSocket(`${websocketUrl}/${t.inkey}`);s.onmessage=this.onWebsocketMessage,s.onopen=()=>console.log("ws connected for wallet",t.id),s.onclose=()=>{console.log("ws closed, reconnecting...",t.id),this.g.walletEventListeners=this.g.walletEventListeners.filter(e=>e!==t.id),clearTimeout(e),e=setTimeout(this.paymentEvents,5e3)},s.onerror=()=>{console.warn("ws error, reconnecting...",t.id),this.g.walletEventListeners=this.g.walletEventListeners.filter(e=>e!==t.id),clearTimeout(e),e=setTimeout(this.paymentEvents,5e3)}}})}}}),window.app.component("lnbits-language-dropdown",{template:"#lnbits-language-dropdown",computed:{currentLanguage(){return this.langs.find(e=>e.value===window.i18n.global.locale)||{value:"en",label:"English",display:"🇬🇧 EN"}}},methods:{activeLanguage:e=>window.i18n.global.locale===e,changeLanguage(e){this.g.locale=e,window.i18n.global.locale=e,this.$q.localStorage.set("lnbits.lang",e),this.$emit("language-changed",e)}},data:()=>({langs:[{value:"en",label:"English",display:"🇬🇧 EN"},{value:"de",label:"Deutsch",display:"🇩🇪 DE"},{value:"es",label:"Español",display:"🇪🇸 ES"},{value:"jp",label:"日本語",display:"🇯🇵 JP"},{value:"cn",label:"中文",display:"🇨🇳 CN"},{value:"fr",label:"Français",display:"🇫🇷 FR"},{value:"it",label:"Italiano",display:"🇮🇹 IT"},{value:"pi",label:"Pirate",display:"🏴‍☠️ PI"},{value:"nl",label:"Nederlands",display:"🇳🇱 NL"},{value:"we",label:"Cymraeg",display:"🏴󠁧󠁢󠁷󠁬󠁳󠁿 CY"},{value:"pl",label:"Polski",display:"🇵🇱 PL"},{value:"pt",label:"Português",display:"🇵🇹 PT"},{value:"br",label:"Português do Brasil",display:"🇧🇷 BR"},{value:"cs",label:"Česky",display:"🇨🇿 CS"},{value:"sk",label:"Slovensky",display:"🇸🇰 SK"},{value:"kr",label:"한국어",display:"🇰🇷 KR"},{value:"fi",label:"Suomi",display:"🇫🇮 FI"}]})}),window.app.component("lnbits-payment-list",{template:"#lnbits-payment-list",props:["wallet","paymentFilter"],data(){return{payments:[],paymentsTable:{columns:[{name:"time",align:"left",label:this.$t("memo")+"/"+this.$t("date"),field:"date",sortable:!0},{name:"amount",align:"right",label:this.$t("amount"),field:"sat",sortable:!0}],pagination:{rowsPerPage:10,page:1,sortBy:"time",descending:!0,rowsNumber:10},sortFields:[{name:"amount",label:"Amount"},{name:"fee",label:"Fee"},{name:"memo",label:"Memo"},{name:"time",label:"Creation Date"},{name:"updated_at",label:"Last Updated"}],search:"",loading:!1},searchDate:{from:null,to:null},searchStatus:{success:!0,pending:!0,failed:!1,incoming:!0,outgoing:!0},exportTagName:"",exportPaymentTagList:[],paymentsCSV:{columns:[{name:"status",align:"right",label:this.$t("status"),field:"status"},{name:"memo",align:"left",label:this.$t("memo"),field:"memo"},{name:"time",align:"left",label:this.$t("date"),field:"date",sortable:!0},{name:"amount",align:"right",label:this.$t("amount"),field:"sat",sortable:!0},{name:"fee",align:"right",label:this.$t("fee"),field:"fee"},{name:"tag",align:"right",label:this.$t("tag"),field:"tag"},{name:"payment_hash",align:"right",label:this.$t("payment_hash"),field:"payment_hash"},{name:"payment_proof",align:"right",label:this.$t("payment_proof"),field:"payment_proof"},{name:"webhook",align:"right",label:this.$t("webhook"),field:"webhook"},{name:"fiat_currency",align:"right",label:"Fiat Currency",field:e=>e.extra.wallet_fiat_currency},{name:"fiat_amount",align:"right",label:"Fiat Amount",field:e=>e.extra.wallet_fiat_amount}],preimage:null,loading:!1},hodlInvoice:{show:!1,payment:null,preimage:null},selectedPayment:null,filterLabels:[]}},computed:{filteredPayments(){const e=this.paymentsTable.search;return e&&""!==e?LNbits.utils.search(this.payments,e):this.payments},paymentsOmitter(){return this.$q.screen.lt.md&&this.g.mobileSimple?this.payments.length>0?[this.payments[0]]:[]:this.payments},pendingPaymentsExist(){return-1!==this.payments.findIndex(e=>e.pending)}},methods:{mapPayment(e){const t={checking_id:e.checking_id,status:e.status,amount:e.amount,fee:e.fee,memo:e.memo,time:e.time,bolt11:e.bolt11,preimage:e.preimage,payment_hash:e.payment_hash,expiry:e.expiry,extra:e.extra??{},wallet_id:e.wallet_id,webhook:e.webhook,webhook_status:e.webhook_status,fiat_amount:e.fiat_amount,fiat_currency:e.fiat_currency,labels:e.labels};t.date=this.utils.formatDate(e.created_at),t.dateFrom=this.utils.formatDateFrom(e.created_at),t.expirydate=this.utils.formatDate(e.expiry),t.expirydateFrom=this.utils.formatDateFrom(e.expiry),t.msat=t.amount,t.sat=t.msat/1e3,t.tag=t.extra?.tag,t.fsat=this.utils.formatSat(t.sat),t.isIn=t.amount>0,t.isOut=t.amount<0,t.isPending="pending"===t.status,t.isPaid="success"===t.status,t.isFailed="failed"===t.status,t._q=[t.memo,t.sat].join(" ").toLowerCase();try{t.details=JSON.parse(e.extra?.details||"{}")}catch{t.details={extraDetails:e.extra?.details}}return t},searchByDate(){"string"==typeof this.searchDate&&(this.searchDate={from:this.searchDate,to:this.searchDate}),this.searchDate.from&&(this.paymentFilter["time[ge]"]=this.searchDate.from+"T00:00:00"),this.searchDate.to&&(this.paymentFilter["time[le]"]=this.searchDate.to+"T23:59:59"),this.fetchPayments()},searchByLabels(e){e&&0!==e.length?(this.filterLabels=e,this.paymentsTable.filter["labels[every]"]=e,this.fetchPayments()):this.clearLabelSeach()},clearDateSeach(){this.searchDate={from:null,to:null},delete this.paymentFilter["time[ge]"],delete this.paymentFilter["time[le]"],this.fetchPayments()},clearLabelSeach(){this.filterLabels=[],delete this.paymentsTable.filter["labels[every]"],this.fetchPayments()},fetchPayments(e){this.paymentsTable.loading=!0;const t=LNbits.utils.prepareFilterQuery(this.paymentsTable,e,this.paymentFilter);return LNbits.api.getPayments(this.wallet,t).then(e=>{this.paymentsTable.pagination.rowsNumber=e.data.total,this.payments=e.data.data.map(this.mapPayment),this.paymentsTable.loading=!1,this.recheckPendingPayments()}).catch(e=>{this.paymentsTable.loading=!1,g.user.admin?this.fetchPaymentsAsAdmin(this.wallet.id,t):LNbits.utils.notifyApiError(e)})},sortByColumn(e){this.paymentsTable.pagination.sortBy===e?this.paymentsTable.pagination.descending=!this.paymentsTable.pagination.descending:(this.paymentsTable.pagination.sortBy=e,this.paymentsTable.pagination.descending=!1),this.fetchPayments()},fetchPaymentsAsAdmin(e,t){return t=(t||"")+"&wallet_id="+e,LNbits.api.request("GET","/api/v1/payments/all/paginated?"+t).then(e=>{this.paymentsTable.loading=!1,this.paymentsTable.pagination.rowsNumber=e.data.total,this.payments=e.data.data.map(this.mapPayment)}).catch(e=>{this.paymentsTable.loading=!1,LNbits.utils.notifyApiError(e)})},checkPayment(e){LNbits.api.getPayment(this.wallet,e).then(e=>{this.update=!this.update,"success"==e.data.status&&Quasar.Notify.create({type:"positive",message:this.$t("payment_successful")}),"pending"==e.data.status&&Quasar.Notify.create({type:"info",message:this.$t("payment_pending")})}).catch(LNbits.utils.notifyApiError)},recheckPendingPayments(){const e=this.payments.filter(e=>"pending"===e.status);if(0===e.length)return;const t=["recheck_pending=true","checking_id[in]="+e.map(e=>e.checking_id).join(",")].join("&");LNbits.api.getPayments(this.wallet,t).then(e=>{let t=0;e.data.data.forEach(e=>{if("pending"!==e.status){const s=this.payments.findIndex(t=>t.checking_id===e.checking_id);-1!==s&&(this.payments.splice(s,1,this.mapPayment(e)),t+=1)}}),t>0&&Quasar.Notify.create({type:"positive",message:this.$t("payment_successful")})}).catch(e=>{console.warn(e)})},showHoldInvoiceDialog(e){this.hodlInvoice.show=!0,this.hodlInvoice.preimage="",this.hodlInvoice.payment=e},cancelHoldInvoice(e){LNbits.api.cancelInvoice(this.wallet,e).then(()=>{this.update=!this.update,Quasar.Notify.create({type:"positive",message:this.$t("invoice_cancelled")})}).catch(LNbits.utils.notifyApiError)},settleHoldInvoice(e){LNbits.api.settleInvoice(this.wallet,e).then(()=>{this.update=!this.update,Quasar.Notify.create({type:"positive",message:this.$t("invoice_settled")})}).catch(LNbits.utils.notifyApiError)},paymentTableRowKey:e=>e.payment_hash+e.amount,async exportCSV(e=!1){const t=this.paymentsTable.pagination,s=1e3;let a=[];this.paymentsCSV.loading=!0;try{for(let e=0;e<100;e++){const i={sortby:t.sortBy??"time",direction:t.descending?"desc":"asc",limit:s,offset:e*s},n=new URLSearchParams(i),o=await LNbits.api.getPayments(this.wallet,n),r=o.data.data||[];if(a=a.concat(r.map(this.mapPayment)),r.length=o.data.total)break}let i=this.paymentsCSV.columns;if(e){this.exportPaymentTagList.length&&(a=a.filter(e=>this.exportPaymentTagList.includes(e.tag)));const e=Object.keys(a.reduce((e,t)=>({...e,...t.details}),{})).map(e=>({name:e,align:"right",label:e.charAt(0).toUpperCase()+e.slice(1).replace(/([A-Z])/g," $1"),field:t=>t.details[e],format:e=>"object"==typeof e?JSON.stringify(e):e}));i=this.paymentsCSV.columns.concat(e)}LNbits.utils.exportCSV(i,a,this.wallet.name+"-payments")}catch(e){LNbits.utils.notifyApiError(e)}finally{this.paymentsCSV.loading=!1}},addFilterTag(){if(!this.exportTagName)return;const e=this.exportTagName.trim();this.exportPaymentTagList=this.exportPaymentTagList.filter(t=>t!==e),this.exportPaymentTagList.push(e),this.exportTagName=""},removeExportTag(e){this.exportPaymentTagList=this.exportPaymentTagList.filter(t=>t!==e)},formatCurrency(e,t){try{return LNbits.utils.formatCurrency(e,t)}catch(t){return console.error(t),`${e} ???`}},handleFilterChanged(){const{success:e,pending:t,failed:s,incoming:a,outgoing:i}=this.searchStatus;let n=this.paymentFilter||{};delete n["status[ne]"],delete n["status[eq]"],e&&t&&s||(e&&t?n["status[ne]"]="failed":e&&s?n["status[ne]"]="pending":s&&t?n["status[ne]"]="success":!e||t||s?!t||e||s?!s||e||t||(n["status[eq]"]="failed"):n["status[eq]"]="pending":n["status[eq]"]="success"),delete n["amount[ge]"],delete n["amount[le]"],a&&i||!a&&!i||(a&&!i?n["amount[ge]"]=0:i&&!a&&(n["amount[le]"]=0)),this.paymentFilter=n},async savePaymentLabels(e){if(this.selectedPayment)try{await LNbits.api.request("PUT",`/api/v1/payments/${this.selectedPayment.payment_hash}/labels`,this.wallet.adminkey,{labels:e});const t=this.payments.find(e=>e.checking_id===this.selectedPayment.checking_id);t&&(t.labels=[...e]),Quasar.Notify.create({type:"positive",message:this.$t("payment_labels_updated")})}catch(e){LNbits.utils.notifyApiError(e)}else Quasar.Notify.create({type:"warning",message:"No payment selected"})},isLightColor(e){try{return Quasar.colors.luminosity(e)>.5}catch(e){return console.warning(e),!1}}},watch:{"paymentsTable.search":{handler(){const e={};this.paymentsTable.search&&(e.search=this.paymentsTable.search),this.fetchPayments()}},"g.updatePayments"(){this.fetchPayments()}},created(){this.fetchPayments()}}),window.app.component("lnbits-label-selector",{template:"#lnbits-label-selector",props:["labels"],data:()=>({labelFilter:"",localLabels:[]}),methods:{toggleLabel(e){if(this.localLabels.includes(e.name)){const t=this.localLabels.indexOf(e.name);-1!==t&&this.localLabels.splice(t,1)}else this.localLabels.push(e.name)},saveLabels(){this.$emit("update:labels",this.localLabels)},clearLabels(){this.localLabels=[],this.saveLabels()}},created(){this.localLabels=[...this.labels]}}),window.app.component("lnbits-extension-settings-form",{name:"lnbits-extension-settings-form",template:"#lnbits-extension-settings-form",props:["options","adminkey","endpoint"],methods:{async updateSettings(){if(!this.settings)return Quasar.Notify.create({message:"No settings to update",type:"negative"});try{const{data:e}=await LNbits.api.request("PUT",this.endpoint,this.adminkey,this.settings);this.settings=e}catch(e){LNbits.utils.notifyApiError(e)}},async getSettings(){try{const{data:e}=await LNbits.api.request("GET",this.endpoint,this.adminkey);this.settings=e}catch(e){LNbits.utils.notifyApiError(e)}},async resetSettings(){LNbits.utils.confirmDialog("Are you sure you want to reset the settings?").onOk(async()=>{try{await LNbits.api.request("DELETE",this.endpoint,this.adminkey),await this.getSettings()}catch(e){LNbits.utils.notifyApiError(e)}})}},async created(){await this.getSettings()},data:()=>({settings:void 0})}),window.app.component("lnbits-extension-settings-btn-dialog",{template:"#lnbits-extension-settings-btn-dialog",name:"lnbits-extension-settings-btn-dialog",props:["options","adminkey","endpoint"],data:()=>({show:!1})}),window.app.component("lnbits-data-fields",{name:"lnbits-data-fields",template:"#lnbits-data-fields",props:["fields","hide-advanced"],data:()=>({fieldTypes:[{label:"Text",value:"str"},{label:"Integer",value:"int"},{label:"Float",value:"float"},{label:"Boolean",value:"bool"},{label:"Date Time",value:"datetime"},{label:"JSON",value:"json"},{label:"Wallet Select",value:"wallet"},{label:"Currency Select",value:"currency"}],fieldsTable:{columns:[{name:"name",align:"left",label:"Field Name",field:"name",sortable:!0},{name:"type",align:"left",label:"Type",field:"type",sortable:!1},{name:"label",align:"left",label:"UI Label",field:"label",sortable:!0},{name:"hint",align:"left",label:"UI Hint",field:"hint",sortable:!1},{name:"optional",align:"left",label:"Optional",field:"optional",sortable:!1}],pagination:{sortBy:"name",rowsPerPage:100,page:1,rowsNumber:100},search:null,hideEmpty:!0}}),methods:{addField:function(){this.fields.push({name:"field_name_"+(this.fields.length+1),type:"text",label:"",hint:"",optional:!0,sortable:!0,searchable:!0,editable:!0,fields:[]})},removeField:function(e){const t=this.fields.indexOf(e);t>-1&&this.fields.splice(t,1)}},async created(){this.hideAdvanced||this.fieldsTable.columns.push({name:"editable",align:"left",label:"UI Editable",field:"editable",sortable:!1},{name:"sortable",align:"left",label:"Sortable",field:"sortable",sortable:!1},{name:"searchable",align:"left",label:"Searchable",field:"searchable",sortable:!1})}}),window.app.component(QrcodeVue),window.app.component("lnbits-extension-rating",{template:"#lnbits-extension-rating",name:"lnbits-extension-rating",props:{rating:{type:Number,default:0},count:{type:Number,default:null},clickable:{type:Boolean,default:!1}},computed:{displayRating(){return Math.round(2*(this.rating||0))/2},hasData(){return null!==this.count&&void 0!==this.count}},methods:{handleClick(){this.clickable&&this.$emit("click")}}}),window.app.component("lnbits-manage",{template:"#lnbits-manage",methods:{isActive:e=>window.location.pathname===e},data:()=>({extensions:[]})}),window.app.component("lnbits-payment-details",{template:"#lnbits-payment-details",props:["payment"],computed:{hasPreimage(){return this.payment.preimage&&"0000000000000000000000000000000000000000000000000000000000000000"!==this.payment.preimage},hasExpiry(){return!!this.payment.expiry},hasSuccessAction(){return this.hasPreimage&&this.payment.extra&&this.payment.extra.success_action},webhookStatusColor(){return this.payment.webhook_status>=300||this.payment.webhook_status<0?"red-10":this.payment.webhook_status?"green-10":"cyan-7"},webhookStatusText(){return this.payment.webhook_status?this.payment.webhook_status:"not sent yet"},hasTag(){return this.payment.extra&&!!this.payment.extra.tag},extras(){if(!this.payment.extra)return[];let e=_.omit(this.payment.extra,["tag","success_action"]);return Object.keys(e).map(t=>({key:t,value:e[t]}))}}}),window.app.component("lnbits-lnurlpay-success-action",{template:"#lnbits-lnurlpay-success-action",props:["payment","success_action"],data(){return{decryptedValue:this.success_action.ciphertext}},mounted(){if("aes"!==this.success_action.tag)return null;this.utils.decryptLnurlPayAES(this.success_action,this.payment.preimage).then(e=>{this.decryptedValue=e})}}),window.app.component("lnbits-notifications-btn",{template:"#lnbits-notifications-btn",props:["pubkey"],data:()=>({isSupported:!1,isSubscribed:!1,isPermissionGranted:!1,isPermissionDenied:!1}),methods:{urlB64ToUint8Array(e){const t=(e+"=".repeat((4-e.length%4)%4)).replace(/\-/g,"+").replace(/_/g,"/"),s=atob(t),a=new Uint8Array(s.length);for(let e=0;et!==e),this.$q.localStorage.set("lnbits.webpush.subscribedUsers",JSON.stringify(t))},isUserSubscribed(e){return(JSON.parse(this.$q.localStorage.getItem("lnbits.webpush.subscribedUsers"))||[]).includes(e)},subscribe(){this.isSupported&&!this.isPermissionDenied&&(Notification.requestPermission().then(e=>{this.isPermissionGranted="granted"===e,this.isPermissionDenied="denied"===e}).catch(console.log),navigator.serviceWorker.ready.then(e=>{navigator.serviceWorker.getRegistration().then(e=>{e.pushManager.getSubscription().then(t=>{if(null===t||!this.isUserSubscribed(this.g.user.id)){const t={applicationServerKey:this.urlB64ToUint8Array(this.pubkey),userVisibleOnly:!0};e.pushManager.subscribe(t).then(e=>{LNbits.api.request("POST","/api/v1/webpush",null,{subscription:JSON.stringify(e)}).then(e=>{this.saveUserSubscribed(e.data.user),this.isSubscribed=!0}).catch(LNbits.utils.notifyApiError)})}}).catch(console.log)})}))},unsubscribe(){navigator.serviceWorker.ready.then(e=>{e.pushManager.getSubscription().then(e=>{e&&LNbits.api.request("DELETE","/api/v1/webpush?endpoint="+btoa(e.endpoint),null).then(()=>{this.removeUserSubscribed(this.g.user.id),this.isSubscribed=!1}).catch(LNbits.utils.notifyApiError)})}).catch(console.log)},checkSupported(){let e="https:"===window.location.protocol,t="serviceWorker"in navigator,s="Notification"in window,a="PushManager"in window;return this.isSupported=e&&t&&s&&a,this.isSupported||console.log("Notifications disabled because requirements are not met:",{HTTPS:e,"Service Worker API":t,"Notification API":s,"Push API":a}),this.isSupported},async updateSubscriptionStatus(){await navigator.serviceWorker.ready.then(e=>{e.pushManager.getSubscription().then(e=>{this.isSubscribed=!!e&&this.isUserSubscribed(this.g.user.id)})}).catch(console.log)}},created(){this.isPermissionDenied="denied"===Notification.permission,this.checkSupported()&&this.updateSubscriptionStatus()}}),window.app.component("lnbits-dynamic-fields",{template:"#lnbits-dynamic-fields",props:["options","modelValue"],data:()=>({formData:null,rules:[e=>!!e||"Field is required"]}),methods:{applyRules(e){return e?this.rules:[]},buildData(e,t={}){return e.reduce((e,s)=>(s.options?.length?e[s.name]=this.buildData(s.options,t[s.name]):e[s.name]=t[s.name]??s.default,e),{})},handleValueChanged(){this.$emit("update:model-value",this.formData)}},created(){this.formData=this.buildData(this.options,this.modelValue)}}),window.app.component("lnbits-dynamic-chips",{template:"#lnbits-dynamic-chips",props:["modelValue"],data:()=>({chip:"",chips:[]}),methods:{addChip(){this.chip&&(this.chips.push(this.chip),this.chip="",this.$emit("update:model-value",this.chips.join(",")))},removeChip(e){this.chips.splice(e,1),this.$emit("update:model-value",this.chips.join(","))}},created(){"string"==typeof this.modelValue?this.chips=this.modelValue.split(","):this.chips=[...this.modelValue]}}),window.app.component("lnbits-update-balance",{template:"#lnbits-update-balance",props:["wallet_id","small_btn"],computed:{admin(){return!0===this.g.user?.super_user}},data:()=>({credit:0}),methods:{updateBalance(e){LNbits.api.updateBalance(e.value,this.wallet_id).then(t=>{if(!0!==t.data.success)throw new Error(t.data);credit=parseInt(e.value),Quasar.Notify.create({type:"positive",message:this.$t("credit_ok",{amount:credit}),icon:null}),this.credit=0,e.value=0,e.set()}).catch(LNbits.utils.notifyApiError)}}}),window.app.component("user-id-only",{template:"#user-id-only",props:{allowed_new_users:Boolean,authAction:String,authMethod:String,usr:String,wallet:String},data(){return{user:this.usr,walletName:this.wallet}},methods:{showLogin(e){this.$emit("show-login",e)},showRegister(e){this.$emit("show-register",e)},loginUsr(){this.$emit("update:usr",this.user),this.$emit("login-usr")},createWallet(){this.$emit("update:wallet",this.walletName),this.$emit("create-wallet")}},computed:{showInstantLogin(){return"username-password"!==this.authMethod||"register"!==this.authAction}},created(){}}),window.app.component("username-password",{template:"#username-password",props:{allowed_new_users:Boolean,authMethods:Array,authAction:String,username:String,password_1:String,password_2:String,invitationCode:String,resetKey:String},data(){return{oauth:["nostr-auth-nip98","google-auth","github-auth","keycloak-auth","oidc-auth"],username:this.userName,password:this.password_1,passwordRepeat:this.password_2,reset_key:this.resetKey,confirmationMethod:"code",confirmationEmail:"",confirmationCode:this.invitationCode||"",showConfirmationCode:!1,showPwd:!1,showPwdRepeat:!1}},methods:{login(){this.$emit("update:userName",this.username),this.$emit("update:password_1",this.password),this.$emit("login")},register(){this.$emit("update:userName",this.username),this.$emit("update:password_1",this.password),this.$emit("update:password_2",this.passwordRepeat),this.$emit("update:invitationCode",this.confirmationCode),this.$emit("register")},reset(){this.$emit("update:resetKey",this.reset_key),this.$emit("update:password_1",this.password),this.$emit("update:password_2",this.passwordRepeat),this.$emit("reset")},validateUsername:e=>new RegExp("^(?=[a-zA-Z0-9._]{2,20}$)(?!.*[_.]{2})[^_.].*[^_.]$").test(e),async signInWithNostr(){try{const e=await this.createNostrToken();if(!e)return;resp=await LNbits.api.loginByProvider("nostr",{Authorization:e},{}),window.location.href="/wallet"}catch(e){console.warn(e);const t=e?.response?.data?.detail||`${e}`;Quasar.Notify.create({type:"negative",message:"Failed to sign in with Nostr.",caption:t})}},async createNostrToken(){try{if(!window.nostr?.signEvent)return void Quasar.Notify.create({type:"negative",message:"No Nostr signing app detected.",caption:'Is "window.nostr" present?'});const e=`${window.location}nostr`,t="POST",s=await NostrTools.nip98.getToken(e,t,e=>async function(e){try{const{data:t}=await LNbits.api.getServerHealth();return e.created_at=t.server_time,await window.nostr.signEvent(e)}catch(e){console.error(e),Quasar.Notify.create({type:"negative",message:"Failed to sign nostr event.",caption:`${e}`})}}(e),!0);if(!await NostrTools.nip98.validateToken(s,e,t))throw new Error("Invalid signed token!");return s}catch(e){console.warn(e),Quasar.Notify.create({type:"negative",message:"Failed create Nostr event.",caption:`${e}`})}}},computed:{showOauth(){return this.oauth.some(e=>this.authMethods.includes(e))},disableRegister(){const e=!!this.username,t=!!this.password&&this.password.length>=8,s=this.password===this.passwordRepeat,a=0===this.confirmationMethodsCount||"code"!==this.confirmationMethod||this.confirmationCode.length>0;return!(e&&t&&s&&a)},confirmationMethodsCount(){return[this.g.settings.userActivationByEmail,this.g.settings.userActivationByPayment,this.g.settings.userActivationByInvitationCode].filter(Boolean).length}},created(){}}),window.app.component("separator-text",{template:"#separator-text",props:{text:String,uppercase:{type:Boolean,default:!1},color:{type:String,default:"grey"}}}),window.app.component("lnbits-node-ranks",{props:["ranks"],data:()=>({stats:[{label:"Capacity",key:"capacity"},{label:"Channels",key:"channelcount"},{label:"Age",key:"age"},{label:"Growth",key:"growth"},{label:"Availability",key:"availability"}]}),template:"\n \n
\n
1ml Node Rank
\n
\n
\n
{{ stat.label }}
\n
\n {{ (ranks && ranks[stat.key]) ?? '-' }}\n
\n
\n
\n
\n \n "}),window.app.component("lnbits-channel-stats",{props:["stats"],data:()=>({states:[{label:"Active",value:"active",color:"green"},{label:"Pending",value:"pending",color:"orange"},{label:"Inactive",value:"inactive",color:"grey"},{label:"Closed",value:"closed",color:"red"}]}),template:"\n \n
\n
Channels
\n
\n
\n
\n {{ state.label }}\n
\n
\n {{ (stats?.counts && stats.counts[state.value]) ?? \"-\" }}\n
\n
\n
\n
\n
\n "}),window.app.component("lnbits-stat",{props:["title","amount","msat","btc"],computed:{value(){return this.amount??(this.btc?LNbits.utils.formatSat(this.btc):LNbits.utils.formatMsat(this.msat))}},template:"\n \n \n
\n {{ title }}\n
\n
\n {{ value }}\n sats\n BTC\n
\n
\n
\n "}),window.app.component("lnbits-node-qrcode",{props:["info"],template:'\n \n \n
\n
\n \n
\n No addresses available\n
\n
\n
\n
\n \n Public Key Click to copy \n \n \n
\n '}),window.app.component("lnbits-channel-balance",{props:["balance","color"],methods:{formatMsat:e=>LNbits.utils.formatMsat(e)},template:'\n
\n
\n \n Local: {{ formatMsat(balance.local_msat) }}\n sats\n \n \n Remote: {{ formatMsat(balance.remote_msat) }}\n sats\n \n
\n\n \n
\n \n {{ balance.alias }}\n \n
\n \n
\n '}),window.app.component("lnbits-node-info",{props:["info"],data:()=>({showDialog:!1}),methods:{shortenNodeId:e=>e?e.substring(0,5)+"..."+e.substring(e.length-5):"..."},template:"\n
\n
{{ this.info.alias }}
\n
\n
{{ this.info.backend_name }}
\n \n #{{ this.info.color }}\n \n
{{ shortenNodeId(this.info.id) }}
\n \n \n
\n \n \n \n
\n "}),window.app.component("lnbits-stat",{props:["title","amount","msat","btc"],computed:{value(){return this.amount??(this.btc?LNbits.utils.formatSat(this.btc):LNbits.utils.formatMsat(this.msat))}},template:"\n \n \n
\n {{ title }}\n
\n
\n {{ value }}\n sats\n BTC\n
\n
\n
\n "}),window.WasmExtensionComponent={template:'\n
\n \n \n \n \n {{ error }}\n \n \n \n \n \n
Camera access
\n
\n \n {{ cameraPrompt.extensionName }} wants to access the camera to scan a QR code.\n \n \n \n \n \n \n
\n
\n \n \n \n
Background payments
\n
\n \n
\n {{ backgroundPaymentPrompt.extensionName }} wants permission to make\n background payments from\n {{ backgroundPaymentPrompt.walletName }}.\n
\n \n This permission can move funds later without an active click.\n \n \n \n
\n \n \n \n \n
\n
\n \n \n \n
Watch wallet payments
\n
\n \n
\n {{ walletPaymentWatchPrompt.extensionName }} wants permission to receive\n payment notifications for\n {{ walletPaymentWatchPrompt.walletName }}.\n
\n \n This permission exposes payment metadata for this wallet to the extension.\n \n
\n \n \n \n \n
\n
\n \n \n \n
Open link
\n
\n \n
\n {{ newTabPrompt.extensionName }} wants to open this link in a new\n tab.\n
\n \n {{ newTabPrompt.url }}\n
\n \n This link is not on the same domain as this LNbits page.\n \n \n \n \n \n \n \n \n \n \n ',data:()=>({allowedPaymentHashes:new Set,bridge:{apiRoutes:[],extensionId:"",permissions:[],public:!1,query:{},routeParams:{}},bridgePort:null,cameraPrompt:{extensionName:"",reject:null,resolve:null,show:!1},backgroundPaymentDestinationOptions:[{label:"Only transfers to my wallets",value:"own_wallets_only"},{label:"Allow external payments",value:"external_allowed"}],backgroundPaymentPrompt:{extensionName:"",form:{destinationPolicy:"own_wallets_only",maxAmount:0},reject:null,resolve:null,show:!1,walletId:"",walletName:""},walletPaymentWatchPrompt:{extensionName:"",reject:null,resolve:null,show:!1,walletId:"",walletName:""},newTabPrompt:{extensionName:"",external:!1,reject:null,resolve:null,show:!1,url:""},error:"",extensionName:"",frameUrl:"",handleWindowMessage:null,loading:!1,loadId:0,paymentSubscriptions:new Map,websocketSubscriptions:new Map}),created(){this.handleWindowMessage=e=>this.onWindowMessage(e),window.addEventListener("message",this.handleWindowMessage)},unmounted(){window.removeEventListener("message",this.handleWindowMessage),this.rejectCameraPrompt("Camera scan cancelled."),this.rejectBackgroundPaymentPrompt("Background payment permission cancelled."),this.rejectWalletPaymentWatchPrompt("Wallet payment watch permission cancelled."),this.rejectNewTabPrompt("Open link cancelled."),this.closeBridgePort()},watch:{"$route.fullPath":{immediate:!0,handler(){this.loadFrameConfig()}}},methods:{emptyBridge:()=>({apiRoutes:[],extensionId:"",permissions:[],public:!1,query:{},routeParams:{}}),plainBridgeContext(){return{extensionId:String(this.bridge.extensionId||""),public:Boolean(this.bridge.public),routeParams:this.plainValue(this.bridge.routeParams||{}),query:this.plainValue(this.bridge.query||{})}},hasBridgePermission(e){return(this.bridge.permissions||[]).includes(e)},cameraPromptStorageKey(){return`lnbits.ext.permissions.${this.bridge.extensionId}.ui.camera.scan_qr`},emptyBackgroundPaymentPrompt:()=>({extensionName:"",form:{destinationPolicy:"own_wallets_only",maxAmount:0},reject:null,resolve:null,show:!1,walletId:"",walletName:""}),emptyCameraPrompt:()=>({extensionName:"",reject:null,resolve:null,show:!1}),emptyWalletPaymentWatchPrompt:()=>({extensionName:"",reject:null,resolve:null,show:!1,walletId:"",walletName:""}),emptyNewTabPrompt:()=>({extensionName:"",external:!1,reject:null,resolve:null,show:!1,url:""}),plainValue(e){try{return JSON.parse(JSON.stringify(e))}catch(e){return{}}},async loadFrameConfig(){const e=String(this.$route.params.extId||""),t=++this.loadId;this.loading=!0,this.error="",this.frameUrl="",this.bridge=this.emptyBridge(),this.allowedPaymentHashes.clear(),this.rejectCameraPrompt("Camera scan cancelled."),this.rejectBackgroundPaymentPrompt("Background payment permission cancelled."),this.rejectWalletPaymentWatchPrompt("Wallet payment watch permission cancelled."),this.rejectNewTabPrompt("Open link cancelled."),this.closeBridgePort();try{const s=await fetch(`/api/v1/ext/${encodeURIComponent(e)}/_ui/frame`,{method:"POST",headers:{"content-type":"application/json"},credentials:"same-origin",body:JSON.stringify({path:this.$route.path,query:this.$route.query||{}})}),a=await s.text();let i={};if(a)try{i=JSON.parse(a)}catch(e){i={detail:a}}if(!s.ok)throw new Error(i?.detail||"Failed to load extension page.");if(t!==this.loadId)return;this.bridge=i.bridge||this.emptyBridge(),this.extensionName=i.extension?.name||e,this.frameUrl=i.frameUrl}catch(e){if(t!==this.loadId)return;console.error("[lnbits wasm extension] Failed to load frame.",e),this.error=e instanceof Error?e.message:String(e)}finally{t===this.loadId&&(this.loading=!1)}},extensionFrameWindow(){return this.$refs.frame?.contentWindow},sendResponse(e,t,s){e({type:"lnbits-extension:response",id:t,...s})},allowedApiRoute(e,t){let s;try{s=new URL(t,window.location.origin)}catch(e){return!1}return s.origin===window.location.origin&&(e=String(e||"GET").toUpperCase(),(this.bridge.apiRoutes||[]).some(t=>t.method===e&&new RegExp(t.pattern).test(s.pathname)))},extensionRoute(e){let t;try{t=new URL(String(e||""),window.location.origin)}catch(e){throw new Error("Invalid extension route.")}if(t.origin!==window.location.origin)throw new Error("Extension route must stay on this server.");const s=`/ext/${encodeURIComponent(this.bridge.extensionId)}`;if(t.pathname!==s&&!t.pathname.startsWith(`${s}/`))throw new Error("Extension route must stay inside this extension.");return`${t.pathname}${t.search}${t.hash}`},replaceExtensionRoute(e){return this.$router.replace(this.extensionRoute(e.path))},newTabUrl(e){const t=String(e||"").trim();if(!t)throw new Error("Open link needs a URL.");let s;try{s=new URL(t,window.location.href)}catch(e){throw new Error("Invalid open link URL.")}if(!["http:","https:"].includes(s.protocol))throw new Error("Only HTTP and HTTPS links can be opened.");if(s.username||s.password)throw new Error("Links with embedded credentials cannot be opened.");return{external:s.origin!==window.location.origin,url:s.href}},openNewTab(e){return this.promptNewTabOpen(this.newTabUrl(e.url||e.href))},promptNewTabOpen(e){if(this.newTabPrompt.show)throw new Error("Open link prompt is already open.");return new Promise((t,s)=>{this.newTabPrompt={extensionName:this.extensionName||this.bridge.extensionId||"This extension",external:e.external,reject:s,resolve:t,show:!0,url:e.url}})},resolveNewTabPrompt(e){const t=this.newTabPrompt;if(t.show)if(this.newTabPrompt=this.emptyNewTabPrompt(),e)try{window.open(t.url,"_blank","noopener,noreferrer"),t.resolve?.({external:t.external,opened:!0,url:t.url})}catch(e){t.reject?.(e)}else t.reject?.(new Error("Open link denied by user."))},rejectNewTabPrompt(e){const t=this.newTabPrompt.reject;this.newTabPrompt=this.emptyNewTabPrompt(),t?.(new Error(e))},async copyNewTabLink(){const e=this.newTabPrompt;if(e.show&&e.url)try{await navigator.clipboard.writeText(e.url),this.notify({level:"positive",message:"Link copied."})}catch(e){this.notify({level:"negative",message:"Could not copy link."})}},bridgeSessionStorageKey(e){const t=String(e||"").trim();if(!t||t.length>128||!/^[A-Za-z0-9._:-]+$/.test(t))throw new Error("Invalid extension session key.");return`lnbits.ext.session.${this.bridge.extensionId}.${t}`},getBridgeSessionValue(e){const t=this.bridgeSessionStorageKey(e.key);return{value:window.sessionStorage.getItem(t)||""}},setBridgeSessionValue(e){const t=this.bridgeSessionStorageKey(e.key),s=String(e.value||"");if(s.length>4096)throw new Error("Extension session value is too large.");return window.sessionStorage.setItem(t,s),{ok:!0}},async callApi(e){const t=String(e.method||"GET").toUpperCase(),s=String(e.path||"");if(!this.allowedApiRoute(t,s))throw new Error("Extension API route is not allowed.");const a={method:t,headers:{},credentials:"same-origin"};void 0!==e.body&&null!==e.body&&(a.headers["content-type"]="application/json",a.body=JSON.stringify(e.body));const i=await fetch(s,a),n=await i.text();let o=n;if(n)try{o=JSON.parse(n)}catch(e){o=n}if(!i.ok)throw new Error("object"==typeof o&&o.detail?o.detail:n);return this.rememberPaymentHashes(o),o},notify(e){const t=["positive","negative","warning","info"].includes(e.level)?e.level:"info";window.Quasar?.Notify&&window.Quasar.Notify.create({color:t,message:String(e.message||"")})},async scanQrCode(){if(!this.hasBridgePermission("ui.camera.scan_qr"))throw new Error("Extension is missing scanner permission.");if(!this.g)throw new Error("LNbits scanner is not available.");if(this.g.scanner)throw new Error("A scanner is already active.");if(await this.requireCameraScanApproval(),this.g.scanner)throw new Error("A scanner is already active.");return new Promise((e,t)=>{let s=!1;const a=()=>{window.clearTimeout(o),window.clearInterval(r),this.g.scanner===n&&(this.g.scanner=null)},i=e=>t=>{s||(s=!0,a(),e(t))},n=t=>{i(e)({value:String(t||"")})},o=window.setTimeout(()=>{i(t)(new Error("QR scan timed out."))},12e4),r=window.setInterval(()=>{s||this.g.scanner===n||i(t)(new Error("QR scan cancelled."))},250);this.g.scanner=n})},async requestBackgroundPaymentPermission(e){const t=await this.requestExtensionPermissions({permissions:[{id:"wallet.pay_invoice_background",grant:e?.grant||{}}]});return t.permissions?.[0]||t},async requestWalletPaymentWatchPermission(e){const t=await this.requestExtensionPermissions({permissions:[{id:"wallet.payments.watch",grant:e?.grant||{}}]});return t.permissions?.[0]||t},async requestExtensionPermissions(e){if(this.bridge.public)throw new Error("Public pages cannot request permissions.");const t=Array.isArray(e.permissions)?e.permissions:[];if(!t.length)throw new Error("No permissions requested.");const s=t.map(e=>this.normalizePermissionRequest(e)),a=await this.checkExtensionPermissions(s.map(e=>({id:e.id,grant:e.grant}))),i=Array.isArray(a?.permissions)?a.permissions:[],n=[],o=[];for(const[e,t]of s.entries()){const s=i[e]||{};if(s.id&&s.id!==t.id)throw new Error("Permission check response did not match request.");if(s.approved){n.push(t.label),o.push({id:t.id,approved:!0,grant:s.grant||t.grant});continue}const a=await this.promptExtensionPermission(t);o.push({id:t.id,approved:!0,grant:a?.grant||t.grant})}return this.notifyApprovedPermissionUse(n),{permissions:o}},normalizePermissionRequest(e){const t=String(e?.id||""),s=e?.grant||{};if("wallet.pay_invoice_background"===t)return this.normalizeBackgroundPaymentPermission(s);if("wallet.payments.watch"===t)return this.normalizeWalletPaymentWatchPermission(s);throw new Error(`Unsupported permission request: ${t}.`)},normalizeBackgroundPaymentPermission(e){if(!this.hasBridgePermission("wallet.pay_invoice_background"))throw new Error("Extension is missing background payment permission.");const t=String(e.walletId||e.wallet_id||""),s=this.walletById(t);if(!s)throw new Error("Selected wallet is not available.");if("lightning-shared"===s.walletType)throw new Error("Background payments are not allowed from shared wallets.");const a={wallet_id:t,max_amount:this.positiveInteger(e.maxAmount||e.max_amount,1e3),destination_policy:this.backgroundPaymentDestinationPolicy(e.destinationPolicy||e.destination_policy)};if(!a.max_amount)throw new Error("Max payment amount must be greater than zero.");return{id:"wallet.pay_invoice_background",grant:a,label:`Background payments from ${s.name||t}`,wallet:s}},normalizeWalletPaymentWatchPermission(e){if(!this.hasBridgePermission("wallet.payments.watch"))throw new Error("Extension is missing wallet payment watch permission.");const t=String(e.walletId||e.wallet_id||""),s=this.walletById(t);if(!s)throw new Error("Selected wallet is not available.");return{id:"wallet.payments.watch",grant:{wallet_id:t},label:`Watch wallet payments for ${s.name||t}`,wallet:s}},walletById(e){return(this.g?.user?.wallets||[]).find(t=>t.id===e)||null},async checkExtensionPermissions(e){return await this.postExtensionPermission("check",{permissions:e},"Could not check extension permissions.")},promptExtensionPermission(e){if("wallet.pay_invoice_background"===e.id)return this.promptBackgroundPaymentPermission(e);if("wallet.payments.watch"===e.id)return this.promptWalletPaymentWatchPermission(e);throw new Error(`Unsupported permission request: ${e.id}.`)},promptBackgroundPaymentPermission(e){if(this.backgroundPaymentPrompt.show)throw new Error("Background payment prompt is already open.");return new Promise((t,s)=>{this.backgroundPaymentPrompt={extensionName:this.extensionName||this.bridge.extensionId||"This extension",form:{destinationPolicy:e.grant.destination_policy,maxAmount:e.grant.max_amount},reject:s,resolve:t,show:!0,walletId:e.grant.wallet_id,walletName:e.wallet.name||e.grant.wallet_id}})},async resolveBackgroundPaymentPrompt(e){const t=this.backgroundPaymentPrompt;if(t.show){if(!e)return this.backgroundPaymentPrompt=this.emptyBackgroundPaymentPrompt(),void t.reject?.(new Error("Background payment permission denied."));try{const e={wallet_id:t.walletId,max_amount:this.positiveInteger(t.form.maxAmount,0),destination_policy:this.backgroundPaymentDestinationPolicy(t.form.destinationPolicy)};if(!e.max_amount)throw new Error("Max payment amount must be greater than zero.");const s=await this.postExtensionPermission("background-payment",e,"Could not save permission.");this.backgroundPaymentPrompt=this.emptyBackgroundPaymentPrompt(),t.resolve?.(s)}catch(e){t.reject?.(e),this.backgroundPaymentPrompt=this.emptyBackgroundPaymentPrompt()}}},rejectBackgroundPaymentPrompt(e){const t=this.backgroundPaymentPrompt.reject;this.backgroundPaymentPrompt=this.emptyBackgroundPaymentPrompt(),t?.(new Error(e))},promptWalletPaymentWatchPermission(e){if(this.walletPaymentWatchPrompt.show)throw new Error("Wallet payment watch prompt is already open.");return new Promise((t,s)=>{this.walletPaymentWatchPrompt={extensionName:this.extensionName||this.bridge.extensionId||"This extension",reject:s,resolve:t,show:!0,walletId:e.grant.wallet_id,walletName:e.wallet.name||e.grant.wallet_id}})},async resolveWalletPaymentWatchPrompt(e){const t=this.walletPaymentWatchPrompt;if(t.show){if(!e)return this.walletPaymentWatchPrompt=this.emptyWalletPaymentWatchPrompt(),void t.reject?.(new Error("Wallet payment watch permission denied."));try{const e=await this.postExtensionPermission("wallet-payments-watch",{wallet_id:t.walletId},"Could not save permission.");this.walletPaymentWatchPrompt=this.emptyWalletPaymentWatchPrompt(),t.resolve?.(e)}catch(e){t.reject?.(e),this.walletPaymentWatchPrompt=this.emptyWalletPaymentWatchPrompt()}}},rejectWalletPaymentWatchPrompt(e){const t=this.walletPaymentWatchPrompt.reject;this.walletPaymentWatchPrompt=this.emptyWalletPaymentWatchPrompt(),t?.(new Error(e))},positiveInteger(e,t){const s=Number(e);return!Number.isFinite(s)||s<=0?t:Math.floor(s)},backgroundPaymentDestinationPolicy:e=>"external_allowed"===e?"external_allowed":"own_wallets_only",async postExtensionPermission(e,t,s){const a=await fetch(`/api/v1/extension/${encodeURIComponent(this.bridge.extensionId)}/permissions/${e}`,{method:"POST",headers:{"content-type":"application/json"},credentials:"same-origin",body:JSON.stringify(t)}),i=await a.text();let n={};if(i)try{n=JSON.parse(i)}catch(e){n={detail:i}}if(!a.ok)throw new Error(n?.detail||s);return n},notifyApprovedPermissionUse(e){const t=e.filter(Boolean).join(", ");t&&this.notify({level:"info",message:`Using approved permissions: ${t}.`})},requireCameraScanApproval(){return this.isCameraScanRemembered()?Promise.resolve():this.cameraPrompt.show?Promise.reject(new Error("Camera access prompt is already open.")):new Promise((e,t)=>{this.cameraPrompt={extensionName:this.extensionName||this.bridge.extensionId||"This extension",reject:t,resolve:e,show:!0}})},isCameraScanRemembered(){try{return"allow"===this.$q.localStorage.getItem(this.cameraPromptStorageKey())}catch(e){return!1}},rememberCameraScanApproval(){try{this.$q.localStorage.set(this.cameraPromptStorageKey(),"allow")}catch(e){}},resolveCameraPrompt(e){const t=this.cameraPrompt.resolve,s=this.cameraPrompt.reject;if(this.cameraPrompt=this.emptyCameraPrompt(),"allow_remember"===e)return this.rememberCameraScanApproval(),void t?.();"allow"!==e?s?.(new Error("Camera scan denied by user.")):t?.()},rejectCameraPrompt(e){const t=this.cameraPrompt.reject;this.cameraPrompt=this.emptyCameraPrompt(),t?.(new Error(e))},rememberPaymentHashes(e){if(e&&"object"==typeof e)if(Array.isArray(e))e.forEach(e=>this.rememberPaymentHashes(e));else for(const[t,s]of Object.entries(e))["paymentHash","payment_hash"].includes(t)&&this.isPaymentHash(s)&&this.allowedPaymentHashes.add(s),this.rememberPaymentHashes(s)},isPaymentHash:e=>"string"==typeof e&&/^[a-f0-9]{64}$/i.test(e),isWebsocketItemId:e=>"string"==typeof e&&/^[A-Za-z0-9][A-Za-z0-9:_-]{0,127}$/.test(e),websocketUrl(e){const t=new URL(window.location.href);return t.protocol="https:"===t.protocol?"wss:":"ws:",t.pathname=e,t.search="",t.hash="",t.toString()},sendBridgeEvent(e){this.bridgePort&&this.bridgePort.postMessage({type:"lnbits-extension:event",...e})},closePaymentSubscription(e){const t=this.paymentSubscriptions.get(e);if(t){this.paymentSubscriptions.delete(e);try{t.socket.close()}catch(e){}}},closePaymentSubscriptions(){for(const e of Array.from(this.paymentSubscriptions.keys()))this.closePaymentSubscription(e)},closeWebsocketSubscription(e){const t=this.websocketSubscriptions.get(e);if(t){this.websocketSubscriptions.delete(e);try{t.socket.close()}catch(e){}}},closeWebsocketSubscriptions(){for(const e of Array.from(this.websocketSubscriptions.keys()))this.closeWebsocketSubscription(e)},closeBridgePort(){this.closePaymentSubscriptions(),this.closeWebsocketSubscriptions(),this.bridgePort?.close(),this.bridgePort=null},subscribePayment(e){const t=String(e.subscriptionId||""),s=String(e.paymentHash||"");if(!t||!this.isPaymentHash(s))throw new Error("Invalid payment subscription.");if(!this.allowedPaymentHashes.has(s))throw new Error("Payment subscription is not allowed.");this.closePaymentSubscription(t);const a=new WebSocket(this.websocketUrl(`/api/v1/ws/${encodeURIComponent(s)}`));this.paymentSubscriptions.set(t,{paymentHash:s,socket:a}),a.addEventListener("message",e=>{let a=e.data;try{a=JSON.parse(e.data)}catch(e){}this.sendBridgeEvent({event:"payment.update",subscriptionId:t,paymentHash:s,data:a}),a&&"object"==typeof a&&(!1===a.pending||["success","settled","paid"].includes(String(a.status||"")))&&(this.sendBridgeEvent({event:"payment.settled",subscriptionId:t,paymentHash:s,data:a}),this.closePaymentSubscription(t))}),a.addEventListener("error",()=>{this.sendBridgeEvent({event:"payment.error",subscriptionId:t,paymentHash:s}),this.closePaymentSubscription(t)}),a.addEventListener("close",()=>{this.paymentSubscriptions.delete(t)})},subscribeWebsocket(e){if(!this.hasBridgePermission("websocket.subscribe"))throw new Error("Extension is missing websocket subscribe permission.");const t=String(e.subscriptionId||""),s=String(e.itemId||"");if(!t||t.length>128||!this.isWebsocketItemId(s))throw new Error("Invalid websocket subscription.");this.closeWebsocketSubscription(t);const a=new WebSocket(this.websocketUrl(`/api/v1/ext/ws/${encodeURIComponent(this.bridge.extensionId)}/${encodeURIComponent(s)}`));this.websocketSubscriptions.set(t,{itemId:s,socket:a}),a.addEventListener("message",e=>{let a=e.data;try{a=JSON.parse(e.data)}catch(e){}this.sendBridgeEvent({event:"websocket.message",subscriptionId:t,itemId:s,data:a})}),a.addEventListener("error",()=>{this.sendBridgeEvent({event:"websocket.error",subscriptionId:t,itemId:s}),this.closeWebsocketSubscription(t)}),a.addEventListener("close",()=>{this.websocketSubscriptions.delete(t)})},sendWebsocket(e){if(!this.hasBridgePermission("websocket.subscribe"))throw new Error("Extension is missing websocket subscribe permission.");const t=String(e.subscriptionId||"");if(!t)throw new Error("Invalid websocket subscription.");const s=this.websocketSubscriptions.get(t);if(!s)return;if(s.socket.readyState!==WebSocket.OPEN)return void(s.socket.readyState!==WebSocket.CLOSING&&s.socket.readyState!==WebSocket.CLOSED||this.closeWebsocketSubscription(t));const a="string"==typeof e.data?e.data:JSON.stringify(e.data??{});s.socket.send(a)},async handleBridgeRequest(e,t){if(e&&"lnbits-extension:request"===e.type)try{if("context"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:this.plainBridgeContext()});if("api"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:await this.callApi(e)});if("ui.notify"===e.action)return this.notify(e),void this.sendResponse(t,e.id,{ok:!0,data:{ok:!0}});if("navigation.replace"===e.action)return await this.replaceExtensionRoute(e),void this.sendResponse(t,e.id,{ok:!0,data:{ok:!0}});if("navigation.open_new_tab"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:await this.openNewTab(e)});if("storage.session.get"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:this.getBridgeSessionValue(e)});if("storage.session.set"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:this.setBridgeSessionValue(e)});if("ui.scan_qr"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:await this.scanQrCode()});if("permissions.request"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:await this.requestExtensionPermissions(e)});if("permissions.request_background_payment"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:await this.requestBackgroundPaymentPermission(e)});if("permissions.request_wallet_payment_watch"===e.action)return void this.sendResponse(t,e.id,{ok:!0,data:await this.requestWalletPaymentWatchPermission(e)});if("payment.subscribe"===e.action)return this.subscribePayment(e),void this.sendResponse(t,e.id,{ok:!0,data:{ok:!0}});if("payment.unsubscribe"===e.action)return this.closePaymentSubscription(String(e.subscriptionId||"")),void this.sendResponse(t,e.id,{ok:!0,data:{ok:!0}});if("websocket.subscribe"===e.action)return this.subscribeWebsocket(e),void this.sendResponse(t,e.id,{ok:!0,data:{ok:!0}});if("websocket.unsubscribe"===e.action)return this.closeWebsocketSubscription(String(e.subscriptionId||"")),void this.sendResponse(t,e.id,{ok:!0,data:{ok:!0}});if("websocket.send"===e.action)return this.sendWebsocket(e),void this.sendResponse(t,e.id,{ok:!0,data:{ok:!0}});throw new Error("Unknown extension bridge action.")}catch(s){this.sendResponse(t,e.id,{ok:!1,error:s instanceof Error?s.message:String(s)})}},onWindowMessage(e){if(e.source!==this.extensionFrameWindow())return;const t=e.data;if(!t||"lnbits-extension:connect"!==t.type)return;const s=e.ports?.[0];s&&(this.closeBridgePort(),this.bridgePort=s,this.bridgePort.addEventListener("message",e=>{this.handleBridgeRequest(e.data,e=>{s.postMessage(e)})}),this.bridgePort.start(),this.bridgePort.postMessage({type:"lnbits-extension:connected",id:t.id}))}}};const quasarConfig={config:{loading:{spinner:Quasar.QSpinnerBars},table:{rowsPerPageOptions:[5,10,20,50,100,200,500,0]}}},DynamicComponent={async created(){const e=this.$route.path.split("/")[1],t=`/${e}/`,s=`/${e}/static/routes.json`;this.$router.getRoutes().some(e=>e.path===t)||this.$route.fullPath.startsWith("/extensions/builder/preview")||fetch(s).then(async e=>{if(!e.ok)throw new Error("No dynamic routes found");(await e.json()).forEach(e=>{console.log("Adding dynamic route:",e.path),window.router.addRoute({path:e.path,name:e.name,component:async()=>{if(await LNbits.utils.loadTemplate(e.template),e.i18n){const t=window.i18n?.global?.locale?.value??window.i18n?.global?.locale??window.g.locale??"en";await LNbits.utils.loadExtI18n(e.i18n,t)}return await LNbits.utils.loadScript(e.component),window[e.name]}}),window.router.push(this.$route.fullPath)})}).catch(()=>{let e=RENDERED_ROUTE;if(2===e.split("/").length&&(e+="/"),e!==this.$route.path)return console.log("Redirecting to non-vue route:",this.$route.fullPath),void(window.location=this.$route.fullPath)})}},routes=[{path:"/node",name:"Node",component:PageNode},{path:"/node/public",name:"NodePublic",component:PageNodePublic},{path:"/payments",name:"Payments",component:PagePayments},{path:"/audit",name:"Audit",component:PageAudit},{path:"/wallet",redirect:e=>{const t=window.g?.lastActiveWallet||window.g?.user?.wallets[0].id;return`/wallet/${e.query.wal||t||"default"}`}},{path:"/wallet/:id",name:"Wallet",component:PageWallet},{path:"/wallets",name:"Wallets",component:PageWallets},{path:"/users",name:"Users",component:PageUsers},{path:"/admin/extensions/wasm",name:"AdminWasmRuntime",component:PageAdmin},{path:"/admin/extensions/wasm/limits",name:"AdminWasmLimitConfig",component:PageAdmin},{path:"/admin/extensions/wasm/limits/:extId",name:"AdminWasmLimitConfigDetail",component:PageAdmin},{path:"/admin/extensions/wasm/:extId",name:"AdminWasmRuntimeDetail",component:PageAdmin},{path:"/admin",name:"Admin",component:PageAdmin},{path:"/account",name:"Account",component:PageAccount},{path:"/extensions/builder",name:"ExtensionsBuilder",component:PageExtensionBuilder},{path:"/extensions/builder/preview",name:"ExtensionsBuilderPreview",component:PageExtensionBuilderPreview},{path:"/extensions",name:"Extensions",component:PageExtensions},{path:"/first_install",name:"FirstInstall",component:PageFirstInstall},{path:"/",name:"PageHome",component:PageHome},{path:"/error",name:"PageError",component:PageError},{path:"/ext/:extId",name:"WasmExtensionRoot",component:window.WasmExtensionComponent},{path:"/ext/:extId/:pathMatch(.*)*",name:"WasmExtension",component:window.WasmExtensionComponent},{path:"/:pathMatch(.*)*",name:"DynamicComponent",component:DynamicComponent}];window.router=VueRouter.createRouter({history:VueRouter.createWebHistory(),routes:routes}),window.LOCALE=window.g.locale,window.i18n=new VueI18n.createI18n({locale:window.g.locale,fallbackLocale:"en",messages:window.localisation}),function(){let e=!1,t=null;Vue.watch(()=>window.i18n.global.locale,async(s,a)=>{!e&&LNbits.utils._extI18nDirs.size&&(t=s,e=!0,window.i18n.global.locale=a,e=!1,await Promise.all([...LNbits.utils._extI18nDirs].map(e=>LNbits.utils.loadExtI18n(e,s))),t===s&&(e=!0,window.i18n.global.locale=s,e=!1))},{flush:"sync"})}(),window.app.mixin({data:()=>({api:window._lnbitsApi,utils:window._lnbitsUtils,g:window.g}),methods:{copyText:window._lnbitsUtils.copyText,formatBalance:window._lnbitsUtils.formatBalance}}),window.app.use(VueQrcodeReader),window.app.use(Quasar,quasarConfig),window.app.use(window.i18n),window.app.use(window.router),window.app.mount("#vue"); \ No newline at end of file diff --git a/lnbits/static/js/wasm-extension-component.js b/lnbits/static/js/wasm-extension-component.js index 2340000ae..e9942516e 100644 --- a/lnbits/static/js/wasm-extension-component.js +++ b/lnbits/static/js/wasm-extension-component.js @@ -1217,13 +1217,23 @@ window.WasmExtensionComponent = { } const subscriptionId = String(message.subscriptionId || '') + if (!subscriptionId) { + throw new Error('Invalid websocket subscription.') + } + const subscription = this.websocketSubscriptions.get(subscriptionId) if (!subscription) { - throw new Error('Unknown websocket subscription.') + return } if (subscription.socket.readyState !== WebSocket.OPEN) { - throw new Error('Websocket subscription is not open.') + if ( + subscription.socket.readyState === WebSocket.CLOSING || + subscription.socket.readyState === WebSocket.CLOSED + ) { + this.closeWebsocketSubscription(subscriptionId) + } + return } const data = diff --git a/tests/unit/test_services_extensions.py b/tests/unit/test_services_extensions.py index b27dc22eb..b74fa428f 100644 --- a/tests/unit/test_services_extensions.py +++ b/tests/unit/test_services_extensions.py @@ -261,6 +261,45 @@ async def test_uninstall_activate_and_deactivate_extensions( assert start_mock.await_count == 1 +@pytest.mark.anyio +async def test_uninstall_wasm_extension_unregisters_live_routes( + tmp_path, settings: Settings, mocker: MockerFixture +): + ext_id = f"wasm_{uuid4().hex[:8]}" + ext_info = make_installable_extension(ext_id) + original_data_folder = settings.lnbits_data_folder + original_extensions_path = settings.lnbits_extensions_path + original_deactivated = set(settings.lnbits_deactivated_extensions) + unregister_routes_mock = mocker.patch( + "lnbits.core.services.extensions.core_app_extra.unregister_wasm_ext_routes" + ) + clean_mock = mocker.patch.object(InstallableExtension, "clean_extension_files") + + try: + settings.lnbits_data_folder = str(tmp_path / "data") + settings.lnbits_extensions_path = str(tmp_path / "code") + ext_dir = tmp_path / "code" / "extensions" / ext_id + ext_dir.mkdir(parents=True, exist_ok=True) + (ext_dir / "config.json").write_text( + json.dumps(_wasm_install_config(ext_id)), + encoding="utf-8", + ) + await create_installed_extension(ext_info) + + await uninstall_extension(ext_id) + + assert await get_installed_extension(ext_id) is None + assert ext_id in settings.lnbits_deactivated_extensions + finally: + await delete_installed_extension(ext_id=ext_id) + settings.lnbits_data_folder = original_data_folder + settings.lnbits_extensions_path = original_extensions_path + settings.lnbits_deactivated_extensions = original_deactivated + + unregister_routes_mock.assert_called_once_with(ext_id) + clean_mock.assert_called_once() + + @pytest.mark.anyio async def test_wasm_invocation_monitoring_marks_stale_once_and_cleans_periodically( settings: Settings, diff --git a/tests/unit/test_wasm_extension_frontend.py b/tests/unit/test_wasm_extension_frontend.py index dfd985b0a..045af910f 100644 --- a/tests/unit/test_wasm_extension_frontend.py +++ b/tests/unit/test_wasm_extension_frontend.py @@ -42,6 +42,8 @@ def test_wasm_frontend_bridge_restricts_api_routes_and_realtime_actions(): assert "message.action === 'websocket.unsubscribe'" in bridge assert "message.action === 'websocket.send'" in bridge assert "sendWebsocket(message)" in bridge + assert "Unknown websocket subscription." not in bridge + assert "if (!subscription) {\n return\n }" in bridge assert "message.action === 'navigation.replace'" in bridge assert "message.action === 'navigation.open_new_tab'" in bridge assert "openNewTab(message)" in bridge diff --git a/tests/unit/test_wasm_extension_loader.py b/tests/unit/test_wasm_extension_loader.py index 469e0dd22..ff395c2ce 100644 --- a/tests/unit/test_wasm_extension_loader.py +++ b/tests/unit/test_wasm_extension_loader.py @@ -104,6 +104,7 @@ def test_wasm_extension_config_accepts_supported_optional_sections(): {"name": "on_invoice_paid", "visibility": "event"}, ], }, + "openapi": "wasm/openapi.json", "events": {"onInvoicePaid": "on_invoice_paid"}, "ui": {"entrypoint": "static/index.html", "sandbox": True}, "sdk": {"frontend_js": "static/lnbits-extension-sdk.js"}, @@ -122,6 +123,7 @@ def test_wasm_extension_config_accepts_supported_optional_sections(): "export": "render", "auth": "public", "path_params": {"item_id": "str"}, + "openapi": "#/routes/render", } ], "permissions": [{"id": "utils.basic", "description": "Basic utils"}], @@ -132,6 +134,8 @@ def test_wasm_extension_config_accepts_supported_optional_sections(): assert parsed.events.on_invoice_paid == "on_invoice_paid" assert parsed.wasm.world == "lnbits-extension" + assert parsed.openapi == "wasm/openapi.json" + assert parsed.api_routes[0].openapi == "#/routes/render" def test_wasm_extension_config_ignores_unknown_permission_fields(): diff --git a/tests/unit/test_wasm_extension_routes.py b/tests/unit/test_wasm_extension_routes.py index 8490d8f51..25f164751 100644 --- a/tests/unit/test_wasm_extension_routes.py +++ b/tests/unit/test_wasm_extension_routes.py @@ -1,11 +1,12 @@ from __future__ import annotations +import json from collections.abc import AsyncIterator from pathlib import Path from typing import cast import pytest -from fastapi import HTTPException, Request +from fastapi import FastAPI, HTTPException, Request from lnbits.core.wasm_ext.routes.api import ( WasmRequestBodyTooLargeError, @@ -14,6 +15,8 @@ from lnbits.core.wasm_ext.routes.api import ( _read_json_object_with_size, _wasm_extension_api_export, _wasm_route_owner_id, + register_wasm_extension_api_routes, + unregister_wasm_extension_api_routes, ) from lnbits.core.wasm_ext.routes.assets import ( WASM_EXTENSION_STATIC_MIME_TYPES, @@ -29,6 +32,7 @@ from lnbits.core.wasm_ext.routes.ui import ( _match_wasm_extension_ui_route, _wasm_extension_bridge_api_routes, _wasm_extension_entrypoint, + register_wasm_extension_ui_routes, ) from lnbits.core.wasm_ext.wasm.config import parse_wasm_extension_config from lnbits.core.wasm_ext.wasm.loader import WasmExtension @@ -173,6 +177,342 @@ def test_wasm_ui_route_matching_and_bridge_public_api_filtering(tmp_path: Path): } +def test_wasm_api_routes_are_included_in_openapi(tmp_path: Path): + app = FastAPI() + app.openapi_schema = {"stale": True} + + register_wasm_extension_api_routes(app, _wasm_extension(tmp_path)) + + assert app.openapi_schema is None + schema = app.openapi() + assert "/api/v1/ext/demoext/public/{item_id}" in schema["paths"] + assert "/api/v1/ext/demoext/private/{item_id}" in schema["paths"] + assert schema["paths"]["/api/v1/ext/demoext/public/{item_id}"]["get"]["tags"] == [ + "Demo" + ] + assert schema["paths"]["/api/v1/ext/demoext/private/{item_id}"]["post"]["tags"] == [ + "Demo" + ] + + +def test_wasm_api_routes_load_openapi_operation_fragment(tmp_path: Path): + app = FastAPI() + openapi_dir = tmp_path / "wasm" + openapi_dir.mkdir() + (openapi_dir / "openapi.json").write_text( + json.dumps( + { + "schemas": { + "DemoItem": { + "type": "object", + "required": ["id", "name"], + "properties": { + "id": {"type": "string"}, + "name": {"type": "string"}, + }, + } + }, + "routes": { + "list_demo_items": { + "summary": "List demo items", + "description": "Returns demo items.", + "operationId": "demoext_list_demo_items", + "tags": ["Ignored"], + "responses": { + "200": { + "description": "Demo item list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/schemas/DemoItem" + }, + } + }, + } + } + }, + } + }, + } + }, + } + ), + encoding="utf-8", + ) + + register_wasm_extension_api_routes( + app, + _wasm_extension( + tmp_path, + openapi="wasm/openapi.json", + extra_exports=[{"name": "list-demo-items", "visibility": "public"}], + api_routes=[ + { + "method": "GET", + "path": "/public/{item_id}", + "export": "list-demo-items", + "auth": "public", + } + ], + ), + ) + + operation = app.openapi()["paths"]["/api/v1/ext/demoext/public/{item_id}"]["get"] + item_schema = operation["responses"]["200"]["content"]["application/json"][ + "schema" + ]["properties"]["items"]["items"] + assert operation["summary"] == "List demo items" + assert operation["description"] == "Returns demo items." + assert operation["operationId"] == "demoext_list_demo_items" + assert operation["tags"] == ["Demo"] + assert item_schema["properties"]["name"] == {"type": "string"} + + +def test_wasm_api_routes_allow_route_openapi_fragment_override(tmp_path: Path): + app = FastAPI() + openapi_dir = tmp_path / "wasm" + openapi_dir.mkdir() + (openapi_dir / "openapi.json").write_text( + json.dumps( + { + "routes": { + "render": { + "summary": "Default render docs", + "operationId": "demoext_render", + }, + "custom-render": { + "summary": "Custom render docs", + "operationId": "demoext_custom_render", + }, + } + } + ), + encoding="utf-8", + ) + + register_wasm_extension_api_routes( + app, + _wasm_extension( + tmp_path, + openapi="wasm/openapi.json", + api_routes=[ + { + "method": "GET", + "path": "/public/{item_id}", + "export": "render", + "auth": "public", + "openapi": "#/routes/custom-render", + } + ], + ), + ) + + operation = app.openapi()["paths"]["/api/v1/ext/demoext/public/{item_id}"]["get"] + assert operation["summary"] == "Custom render docs" + assert operation["operationId"] == "demoext_custom_render" + + +def test_wasm_api_routes_add_success_response_example_for_redoc(tmp_path: Path): + app = FastAPI() + openapi_dir = tmp_path / "wasm" + openapi_dir.mkdir() + (openapi_dir / "openapi.json").write_text( + json.dumps( + { + "schemas": { + "DemoItem": { + "type": "object", + "properties": { + "id": {"type": "string"}, + "count": {"type": "integer"}, + }, + }, + "DemoResponse": { + "type": "object", + "required": ["ok", "data"], + "properties": { + "ok": {"type": "boolean", "enum": [True]}, + "data": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": {"$ref": "#/schemas/DemoItem"}, + } + }, + }, + }, + }, + "ErrorResponse": { + "type": "object", + "required": ["ok", "error"], + "properties": { + "ok": {"type": "boolean", "enum": [False]}, + "error": {"type": "string"}, + }, + }, + }, + "routes": { + "render": { + "summary": "Render", + "responses": { + "200": { + "description": "Success or extension-level error.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + {"$ref": "#/schemas/DemoResponse"}, + {"$ref": "#/schemas/ErrorResponse"}, + ] + } + } + }, + } + }, + } + }, + } + ), + encoding="utf-8", + ) + + register_wasm_extension_api_routes( + app, + _wasm_extension( + tmp_path, + openapi="wasm/openapi.json", + api_routes=[ + { + "method": "GET", + "path": "/public/{item_id}", + "export": "render", + "auth": "public", + } + ], + ), + ) + + json_content = app.openapi()["paths"]["/api/v1/ext/demoext/public/{item_id}"][ + "get" + ]["responses"]["200"]["content"]["application/json"] + assert json_content["example"] == { + "ok": True, + "data": {"items": [{"id": "string", "count": 0}]}, + } + + +def test_wasm_api_routes_ignore_missing_openapi_fragment(tmp_path: Path): + app = FastAPI() + + register_wasm_extension_api_routes( + app, + _wasm_extension( + tmp_path, + api_routes=[ + { + "method": "GET", + "path": "/public/{item_id}", + "export": "render", + "auth": "public", + "openapi": "wasm/missing.json#/routes/list_demo_items", + } + ], + ), + ) + + operation = app.openapi()["paths"]["/api/v1/ext/demoext/public/{item_id}"]["get"] + assert operation["summary"] == "GET /public/{item_id}" + assert operation["operationId"] == "demoext_get_public_item_id" + + +def test_wasm_api_routes_replace_same_extension_routes_on_upgrade(tmp_path: Path): + app = FastAPI() + route_path = "/api/v1/ext/demoext/public/{item_id}" + + async def legacy_handler() -> dict[str, bool]: + return {"legacy": True} + + app.add_api_route( + route_path, + legacy_handler, + methods=["GET"], + name=f"demoext:GET:{route_path}", + include_in_schema=False, + ) + app.openapi_schema = {"stale": True} + + register_wasm_extension_api_routes(app, _wasm_extension(tmp_path)) + + routes = _matching_routes(app, route_path, "GET") + assert len(routes) == 1 + assert routes[0].endpoint != legacy_handler + assert routes[0].include_in_schema is True + assert app.openapi_schema is None + assert route_path in app.openapi()["paths"] + + +def test_wasm_api_routes_remove_obsolete_routes_on_upgrade(tmp_path: Path): + app = FastAPI() + route_path = "/api/v1/ext/demoext/removed" + + async def removed_handler() -> dict[str, bool]: + return {"removed": True} + + app.add_api_route( + route_path, + removed_handler, + methods=["GET"], + name=f"demoext:GET:{route_path}", + ) + app.openapi_schema = {"stale": True} + + register_wasm_extension_api_routes(app, _wasm_extension(tmp_path)) + + assert _matching_routes(app, route_path, "GET") == [] + assert app.openapi_schema is None + assert route_path not in app.openapi()["paths"] + + +def test_wasm_api_route_cleanup_preserves_ui_frame_config_route(tmp_path: Path): + app = FastAPI() + (tmp_path / "index.html").write_text("", encoding="utf-8") + extension = _wasm_extension(tmp_path) + frame_config_path = "/api/v1/ext/demoext/_ui/frame" + api_route_path = "/api/v1/ext/demoext/public/{item_id}" + + register_wasm_extension_ui_routes(app, extension) + assert _matching_routes(app, frame_config_path, "POST") != [] + + register_wasm_extension_api_routes(app, extension) + + assert _matching_routes(app, frame_config_path, "POST") != [] + assert _matching_routes(app, api_route_path, "GET") != [] + + assert unregister_wasm_extension_api_routes(app, "demoext") is True + assert _matching_routes(app, api_route_path, "GET") == [] + assert _matching_routes(app, frame_config_path, "POST") != [] + + +def test_wasm_api_routes_are_removed_from_openapi_on_uninstall(tmp_path: Path): + app = FastAPI() + route_path = "/api/v1/ext/demoext/public/{item_id}" + + register_wasm_extension_api_routes(app, _wasm_extension(tmp_path)) + assert route_path in app.openapi()["paths"] + + assert unregister_wasm_extension_api_routes(app, "demoext") is True + + assert app.openapi_schema is None + assert _matching_routes(app, route_path, "GET") == [] + assert route_path not in app.openapi()["paths"] + + @pytest.mark.anyio async def test_wasm_api_route_owner_context_uses_configured_storage_row( tmp_path: Path, mocker @@ -260,46 +600,54 @@ class _FakeRequest: yield chunk -def _wasm_extension(root_path: Path) -> WasmExtension: - config = parse_wasm_extension_config( - "demoext", - { - "id": "demoext", - "name": "Demo", - "short_description": "Demo extension", - "version": "1.0.0", - "extension_type": "wasm", - "wasm": { - "module": "extension.wasm", - "exports": [ - {"name": "render", "visibility": "public"}, - {"name": "private_render", "visibility": "authenticated"}, - {"name": "on_invoice_paid", "visibility": "event"}, - ], - }, - "ui_routes": [ - { - "path": "/demo/{item_id}", - "entrypoint": "index.html", - "auth": "user", - } - ], - "api_routes": [ - { - "method": "GET", - "path": "/public/{item_id}", - "export": "render", - "auth": "public", - }, - { - "method": "POST", - "path": "/private/{item_id}", - "export": "private_render", - "auth": "user", - }, +def _wasm_extension( + root_path: Path, + *, + api_routes: list[dict] | None = None, + openapi: str | None = None, + extra_exports: list[dict] | None = None, +) -> WasmExtension: + extension_config = { + "id": "demoext", + "name": "Demo", + "short_description": "Demo extension", + "version": "1.0.0", + "extension_type": "wasm", + "wasm": { + "module": "extension.wasm", + "exports": [ + {"name": "render", "visibility": "public"}, + {"name": "private_render", "visibility": "authenticated"}, + {"name": "on_invoice_paid", "visibility": "event"}, + *(extra_exports or []), ], }, - ) + "ui_routes": [ + { + "path": "/demo/{item_id}", + "entrypoint": "index.html", + "auth": "user", + } + ], + "api_routes": api_routes + or [ + { + "method": "GET", + "path": "/public/{item_id}", + "export": "render", + "auth": "public", + }, + { + "method": "POST", + "path": "/private/{item_id}", + "export": "private_render", + "auth": "user", + }, + ], + } + if openapi: + extension_config["openapi"] = openapi + config = parse_wasm_extension_config("demoext", extension_config) return WasmExtension( id="demoext", name="Demo", @@ -325,3 +673,12 @@ def _request_with_query(token: str) -> Request: "headers": [], } ) + + +def _matching_routes(app: FastAPI, route_path: str, method: str) -> list: + return [ + route + for route in app.router.routes + if getattr(route, "path", None) == route_path + and method in (getattr(route, "methods", set()) or set()) + ] diff --git a/tests/unit/test_wasm_extension_websockets.py b/tests/unit/test_wasm_extension_websockets.py index c7e6b4a7d..94449839a 100644 --- a/tests/unit/test_wasm_extension_websockets.py +++ b/tests/unit/test_wasm_extension_websockets.py @@ -10,16 +10,23 @@ from lnbits.core.wasm_ext.api.websockets import ( class FakeWebSocket: - def __init__(self, received: list[str] | None = None): + def __init__( + self, + received: list[str] | None = None, + send_error: Exception | None = None, + ): self.accepted = False self.sent: list[str] = [] self.closed: int | None = None self.received = list(received or []) + self.send_error = send_error async def accept(self): self.accepted = True async def send_text(self, data: str): + if self.send_error: + raise self.send_error self.sent.append(data) async def receive_text(self): @@ -55,6 +62,26 @@ async def test_wasm_extension_websocket_hub_publishes_to_matching_channel(): assert other_extension.sent == [] +@pytest.mark.anyio +async def test_wasm_extension_websocket_hub_prunes_stale_publish_connections(): + hub = WasmExtensionWebsocketHub() + stale = FakeWebSocket(send_error=RuntimeError("websocket closed")) + active = FakeWebSocket() + + await hub.connect("demoext", "room-1", cast(WebSocket, stale)) + await hub.connect("demoext", "room-1", cast(WebSocket, active)) + + await hub.publish( + "demoext", + "room-1", + '{"message":"Hello"}', + max_messages_per_second=10, + ) + + assert active.sent == ['{"message":"Hello"}'] + assert hub.get_connections("demoext", "room-1")[0].websocket == active + + @pytest.mark.anyio async def test_wasm_extension_websocket_hub_rate_limits_per_channel(): hub = WasmExtensionWebsocketHub()