diff --git a/docs/devs/agents_extensions.md b/docs/devs/agents_extensions.md index d53fc5dcd..6652738aa 100644 --- a/docs/devs/agents_extensions.md +++ b/docs/devs/agents_extensions.md @@ -10,11 +10,13 @@ nav_order: 4 This guide is for AI agents or developers using AI to build **traditional (Python) LNbits extensions**. It defines what to change, what not to change, and the expected structure. ## Hard Rules (Non-Negotiable) + - Do **not** change core LNbits files. - Only edit files inside your extension folder. - Do **not** add new Python dependencies unless explicitly approved. ## Extension Folder Layout (Python) + Your extension lives under: ``` @@ -22,6 +24,7 @@ lnbits/extensions// ``` Typical files to edit: + - `views.py` (HTML routes) - `views_api.py` (API routes) - `crud.py` / `models.py` (storage logic + models) @@ -31,18 +34,22 @@ Typical files to edit: - `config.json`, `manifest.json`, `README.md` ## What You Can Do + Python extensions can: + - Define their own database schema via `migrations.py` - Run long-running background tasks via `*_start()` and `*_stop()` hooks - Access LNbits internal services directly in Python - Expose custom API routes under `//api/v1/...` ## What You Must Not Do + - Do not modify core services or routes. - Do not patch LNbits internals for your extension. - Avoid direct DB access outside your own schema. ## Background Tasks + Implement background tasks by exposing: ``` @@ -53,6 +60,7 @@ def _stop(): Use `register_invoice_listener` or `wait_for_paid_invoices` if you need to react to payments. ## Testing Checklist + - Extension loads without errors. - Migrations apply cleanly. - Routes are registered under `//...`. diff --git a/docs/devs/agents_wasm_extensions.md b/docs/devs/agents_wasm_extensions.md index 9f277b0d7..7e6f8ebda 100644 --- a/docs/devs/agents_wasm_extensions.md +++ b/docs/devs/agents_wasm_extensions.md @@ -10,11 +10,13 @@ nav_order: 3 This guide is written for AI agents or developers using AI to build LNbits WASM extensions. It describes what to change, what not to change, and the available capabilities/limits. ## Hard Rules (Non-Negotiable) + - Do **not** change core LNbits files. Only edit files inside your extension folder. - Do **not** add new Python dependencies. - Do **not** rely on long-running WASM processes. WASM runs per-call with timeouts. ## Extension Folder Layout (WASM) + Your extension lives under: ``` @@ -22,6 +24,7 @@ lnbits/extensions// ``` You should only edit files under this folder, typically: + - `config.json` (metadata, permissions, tags, public handlers) - `wasm/` (your `module.wasm` or `module.wat`) - `static/` (frontend assets) @@ -29,7 +32,9 @@ You should only edit files under this folder, typically: - `manifest.json`, `README.md`, `description.md` (docs and metadata) ## Required Config Fields + In `config.json`: + - `id` / `name` - `extension_type: "wasm"` - `permissions` (required API permissions) @@ -38,6 +43,7 @@ In `config.json`: - `payment_tags` (list of tags the user may grant for watcher access) Example: + ```json { "id": "myext", @@ -45,19 +51,37 @@ Example: "extension_type": "wasm", "permissions": [ {"id": "ext.db.read_write", "label": "DB access", "description": "..."}, - {"id": "api.POST:/api/v1/payments", "label": "Create invoices", "description": "..."}, - {"id": "ext.payments.watch", "label": "Watch payments", "description": "..."}, - {"id": "ext.tasks.schedule", "label": "Schedule tasks", "description": "..."}, + { + "id": "api.POST:/api/v1/payments", + "label": "Create invoices", + "description": "..." + }, + { + "id": "ext.payments.watch", + "label": "Watch payments", + "description": "..." + }, + { + "id": "ext.tasks.schedule", + "label": "Schedule tasks", + "description": "..." + }, {"id": "ext.db.sql", "label": "SQL access", "description": "..."} ], - "public_wasm_functions": ["public_create_invoice", "on_tag_payment", "on_schedule"], + "public_wasm_functions": [ + "public_create_invoice", + "on_tag_payment", + "on_schedule" + ], "public_kv_keys": ["public_lists", "public_tasks"], "payment_tags": ["coinflip", "myext"] } ``` ## What the WASM Host Can Do + WASM runs in a short-lived subprocess. It can: + - Read/write extension KV (`/api/v1/kv/*`) - Read/write secret KV (`/api/v1/secret/*`) - Call internal LNbits endpoints (only if declared + granted) @@ -65,7 +89,9 @@ WASM runs in a short-lived subprocess. It can: - Run backend tag watchers and scheduled handlers (server-side triggers) ## Permissions Model + Your extension can only call or access what is declared and granted: + - `api.METHOD:/path` for internal endpoints (core or other extensions) - `ext.db.read_write` for KV access - `ext.payments.watch` for payment watchers @@ -75,6 +101,7 @@ Your extension can only call or access what is declared and granted: If the endpoint doesn’t exist, permissions won’t save. ## Tag Watchers (Backend) + You can register tag watchers: ``` @@ -88,10 +115,12 @@ POST //api/v1/watch_tag ``` Constraints: + - Tag must be in `payment_tags` and granted by the user. - Watchers are persisted and restored on restart. ## Scheduled Tasks (Backend) + You can schedule periodic handlers: ``` @@ -104,32 +133,40 @@ POST //api/v1/schedule ``` Constraints: + - Requires `ext.tasks.schedule` permission. - Minimum interval is 5 seconds. - Stored in extension KV and restored on restart. ## SQL Interface (Limited) + You can run SQL within your extension schema: + - `/api/v1/sql/query` (SELECT only) - `/api/v1/sql/exec` (limited DDL/DML) Rules: + - Single statement only - No `PRAGMA`, no `sqlite_master` - No cross-schema access ## Public Pages (No Keys) + Public pages must not depend on `window.g` or wallet keys. They can call: + - `/{ext_id}/api/v1/public/kv/{key}` - `/{ext_id}/api/v1/public/call/{handler}` ## What Not To Do + - Do not write to core routes or override existing LNbits paths. - Do not add background threads; use watchers or scheduler instead. - Do not assume the WASM process persists. ## Testing Checklist + - Permissions show correctly in the extensions UI. - Public handlers are in `public_wasm_functions`. - Public KV keys are explicitly listed. diff --git a/lnbits/app.py b/lnbits/app.py index 2de12c69e..0811b5f00 100644 --- a/lnbits/app.py +++ b/lnbits/app.py @@ -36,9 +36,11 @@ from lnbits.core.tasks import ( purge_audit_data, run_by_the_minute_tasks, wait_for_audit_data, - wait_for_paid_invoices, wait_notification_messages, ) +from lnbits.core.tasks import ( + wait_for_paid_invoices as wait_for_paid_invoices_core, +) from lnbits.core.wasm.extension_host import ( handle_wasm_tag_payment, register_wasm_ext_routes, @@ -52,6 +54,9 @@ from lnbits.tasks import ( create_permanent_task, register_invoice_listener, ) +from lnbits.tasks import ( + wait_for_paid_invoices as wait_for_paid_invoices_listener, +) from lnbits.utils.cache import cache from lnbits.utils.logger import ( configure_logger, @@ -504,10 +509,12 @@ def register_async_tasks() -> None: # core invoice listener invoice_queue: asyncio.Queue = asyncio.Queue() register_invoice_listener(invoice_queue, "core") - create_permanent_task(lambda: wait_for_paid_invoices(invoice_queue)) + create_permanent_task(lambda: wait_for_paid_invoices_core(invoice_queue)) # wasm tag watcher listener - create_permanent_task(wait_for_paid_invoices("wasm_tags", handle_wasm_tag_payment)) + create_permanent_task( + wait_for_paid_invoices_listener("wasm_tags", handle_wasm_tag_payment) + ) create_permanent_task(wasm_scheduler) create_permanent_task(run_by_the_minute_tasks) diff --git a/lnbits/core/models/extensions.py b/lnbits/core/models/extensions.py index 0c609e14a..4c9b48423 100644 --- a/lnbits/core/models/extensions.py +++ b/lnbits/core/models/extensions.py @@ -195,9 +195,9 @@ class Extension(BaseModel): upgrade_hash=ext_info.hash if ext_info.ext_upgrade_dir.is_dir() else "", extension_type=ext_info.meta.extension_type if ext_info.meta else None, public_kv_keys=ext_info.meta.public_kv_keys if ext_info.meta else [], - public_wasm_functions=ext_info.meta.public_wasm_functions - if ext_info.meta - else [], + public_wasm_functions=( + ext_info.meta.public_wasm_functions if ext_info.meta else [] + ), payment_tags=ext_info.meta.payment_tags if ext_info.meta else [], ) diff --git a/lnbits/core/services/extensions.py b/lnbits/core/services/extensions.py index c8f1e7608..b265ede81 100644 --- a/lnbits/core/services/extensions.py +++ b/lnbits/core/services/extensions.py @@ -21,7 +21,7 @@ from lnbits.core.wasm.extension_host import ( clear_schedules_for_extension, clear_tag_watches_for_extension, ) -from lnbits.db import Connection, Database, COCKROACH, POSTGRES +from lnbits.db import COCKROACH, POSTGRES, Connection, Database from lnbits.settings import settings from ..models.extensions import Extension, ExtensionMeta, InstallableExtension @@ -93,7 +93,7 @@ async def _purge_wasm_extension_db(ext_id: str) -> None: try: db = Database(f"ext_{ext_id}") if db.type in {POSTGRES, COCKROACH}: - await db.execute(f"DROP SCHEMA IF EXISTS {ext_id} CASCADE") # noqa: S608 + await db.execute(f"DROP SCHEMA IF EXISTS {ext_id} CASCADE") except Exception as exc: logger.warning(f"Failed to drop WASM extension schema for '{ext_id}': {exc}") diff --git a/lnbits/core/views/extension_api.py b/lnbits/core/views/extension_api.py index 21154d99c..d482285af 100644 --- a/lnbits/core/views/extension_api.py +++ b/lnbits/core/views/extension_api.py @@ -8,8 +8,8 @@ import httpx from bolt11 import decode as bolt11_decode from fastapi import APIRouter, Body, Depends, HTTPException from fastapi.requests import Request -from starlette.routing import Match from loguru import logger +from starlette.routing import Match from lnbits.core.crud.extensions import get_user_extensions from lnbits.core.crud.wallets import get_wallets_ids @@ -35,7 +35,6 @@ from lnbits.core.models.extensions import ( UserExtension, UserExtensionInfo, ) -from lnbits.core.wasm import WASM_HOST_MANIFEST from lnbits.core.models.users import Account, AccountId from lnbits.core.services import check_transaction_status, create_invoice from lnbits.core.services.extensions import ( @@ -46,6 +45,7 @@ from lnbits.core.services.extensions import ( install_extension, uninstall_extension, ) +from lnbits.core.wasm import WASM_HOST_MANIFEST from lnbits.core.wasm.extension_host import ( clear_schedules_for_user, clear_tag_watches_for_user, @@ -976,7 +976,8 @@ def _route_exists(request: Request, method: str, path: str) -> bool: continue try: match, _ = route.matches(scope) - except Exception: + except Exception as exc: + logger.debug(f"Route match failed for {method} {path}: {exc!s}") continue if match == Match.FULL: return True diff --git a/lnbits/core/views/websocket_api.py b/lnbits/core/views/websocket_api.py index 88d2f7cb8..f08bdf718 100644 --- a/lnbits/core/views/websocket_api.py +++ b/lnbits/core/views/websocket_api.py @@ -15,7 +15,9 @@ async def websocket_connect(websocket: WebSocket, item_id: str) -> None: @websocket_router.websocket("/tag/{tag}") async def websocket_connect_tag(websocket: WebSocket, tag: str) -> None: - api_key = websocket.headers.get("X-API-KEY") or websocket.query_params.get("api-key") + api_key = websocket.headers.get("X-API-KEY") or websocket.query_params.get( + "api-key" + ) if not api_key: await websocket.close(code=4401) return diff --git a/lnbits/core/wasm/extension_host.py b/lnbits/core/wasm/extension_host.py index 0b55ed02c..52d94e9d4 100644 --- a/lnbits/core/wasm/extension_host.py +++ b/lnbits/core/wasm/extension_host.py @@ -4,22 +4,22 @@ import asyncio import json import re import time -from pathlib import Path from dataclasses import dataclass +from pathlib import Path from typing import Any import httpx from fastapi import APIRouter, Depends, HTTPException, Request from fastapi.responses import HTMLResponse, Response from fastapi.staticfiles import StaticFiles +from loguru import logger from lnbits.core.crud.extensions import get_user_extension +from lnbits.core.crud.payments import get_standalone_payment, update_payment +from lnbits.core.crud.wallets import get_wallet from lnbits.core.models import User from lnbits.core.models.payments import Payment -from lnbits.core.crud.wallets import get_wallet -from lnbits.core.crud.payments import get_standalone_payment, update_payment from lnbits.core.services import websocket_updater -from loguru import logger from lnbits.db import Database from lnbits.decorators import check_user_exists from lnbits.helpers import template_renderer @@ -42,6 +42,7 @@ class TagWatch: _tag_watchers: dict[tuple[str, str], list[TagWatch]] = {} _TAG_WATCH_KV_KEY = "watch_tags" +_background_tasks: list[asyncio.Task] = [] @dataclass @@ -323,19 +324,23 @@ async def handle_wasm_tag_payment(payment: Payment) -> None: if not watchers: return for watch in watchers: - asyncio.create_task(_dispatch_tag_watch(payment, watch)) + _background_tasks.append( + asyncio.create_task(_dispatch_tag_watch(payment, watch)) + ) async def _dispatch_tag_watch(payment: Payment, watch: TagWatch) -> None: try: user_ext = await get_user_extension(watch.user_id, watch.ext_id) if not user_ext or not user_ext.active: - _remove_tag_watch(payment.wallet_id, payment.tag, watch) + if payment.tag: + _remove_tag_watch(payment.wallet_id, payment.tag, watch) await _remove_persisted_tag_watch(watch.ext_id, watch) return granted_tags = user_ext.extra.granted_payment_tags if user_ext.extra else [] if watch.tag not in (granted_tags or []): - _remove_tag_watch(payment.wallet_id, payment.tag, watch) + if payment.tag: + _remove_tag_watch(payment.wallet_id, payment.tag, watch) await _remove_persisted_tag_watch(watch.ext_id, watch) return @@ -409,7 +414,9 @@ async def clear_tag_watches_for_user(ext_id: str, user_id: str) -> None: keys = list(_tag_watchers.keys()) for wallet_id, tag in keys: existing = _tag_watchers.get((wallet_id, tag), []) - remaining = [w for w in existing if not (w.ext_id == ext_id and w.user_id == user_id)] + remaining = [ + w for w in existing if not (w.ext_id == ext_id and w.user_id == user_id) + ] if remaining: _tag_watchers[(wallet_id, tag)] = remaining else: @@ -488,11 +495,13 @@ async def restore_tag_watches(ext_id: str, upgrade_hash: str | None) -> None: async def wasm_scheduler() -> None: while settings.lnbits_running: now = time.time() - for ext_id, tasks in list(_scheduled_tasks.items()): + for _ext_id, tasks in list(_scheduled_tasks.items()): for task in list(tasks): if task.next_run > now: continue - asyncio.create_task(_dispatch_schedule_task(task)) + _background_tasks.append( + asyncio.create_task(_dispatch_schedule_task(task)) + ) task.next_run = now + max(1, task.interval_seconds) await asyncio.sleep(1) @@ -501,14 +510,18 @@ async def _dispatch_schedule_task(task: ScheduleTask) -> None: try: user_ext = await get_user_extension(task.user_id, task.ext_id) if not user_ext or not user_ext.active: - _remove_schedule_entries(task.ext_id, task.user_id, task.handler, task.store_key) + _remove_schedule_entries( + task.ext_id, task.user_id, task.handler, task.store_key + ) await _remove_persisted_schedule_entries( task.ext_id, task.user_id, task.handler, task.store_key ) return granted = user_ext.extra.granted_permissions if user_ext.extra else [] if "ext.tasks.schedule" not in (granted or []): - _remove_schedule_entries(task.ext_id, task.user_id, task.handler, task.store_key) + _remove_schedule_entries( + task.ext_id, task.user_id, task.handler, task.store_key + ) await _remove_persisted_schedule_entries( task.ext_id, task.user_id, task.handler, task.store_key ) @@ -839,9 +852,9 @@ def _register_public_call_routes( ) -> None: @router.post("/api/v1/public/call/{handler}") async def api_public_wasm_call(handler: str, payload: dict): - funcs = getattr(ext, "public_wasm_functions", None) or _load_public_wasm_functions( - ext_id - ) + funcs = getattr( + ext, "public_wasm_functions", None + ) or _load_public_wasm_functions(ext_id) if handler not in funcs: raise HTTPException(404, "Handler not public") _check_quota("public", ext_id, "db", settings.lnbits_wasm_max_db_ops_per_min) @@ -936,7 +949,9 @@ def _register_kv_write_routes(router: APIRouter, ext_id: str, db: Database) -> N return {"key": key, "value": value} -def _register_watch_routes(router: APIRouter, ext_id: str, db: Database, ext) -> None: +def _register_watch_routes( # noqa: C901 + router: APIRouter, ext_id: str, db: Database, ext +) -> None: @router.post("/api/v1/watch") async def api_watch_payment(payload: dict, user: User = Depends(check_user_exists)): payment_hash = payload.get("payment_hash") @@ -971,9 +986,9 @@ def _register_watch_routes(router: APIRouter, ext_id: str, db: Database, ext) -> if not wallet or wallet.user != user.id: raise HTTPException(403, "Wallet not found or not owned by user") - funcs = getattr(ext, "public_wasm_functions", None) or _load_public_wasm_functions( - ext_id - ) + funcs = getattr( + ext, "public_wasm_functions", None + ) or _load_public_wasm_functions(ext_id) if handler not in funcs: raise HTTPException(400, "Handler not allowed") @@ -1022,9 +1037,7 @@ def _register_watch_routes(router: APIRouter, ext_id: str, db: Database, ext) -> wallet = await get_wallet(wallet_id) if not wallet or wallet.user != user.id: raise HTTPException(403, "Wallet not found or not owned by user") - _remove_tag_watch_entries( - ext_id, user.id, wallet_id, tag, handler, store_key - ) + _remove_tag_watch_entries(ext_id, user.id, wallet_id, tag, handler, store_key) await _remove_persisted_tag_watch_entries( ext_id, user.id, wallet_id, tag, handler, store_key ) @@ -1040,16 +1053,16 @@ def _register_watch_routes(router: APIRouter, ext_id: str, db: Database, ext) -> try: interval_seconds = int(interval_seconds) except Exception: - raise HTTPException(400, "Invalid interval_seconds") + raise HTTPException(400, "Invalid interval_seconds") from None if interval_seconds < 5: raise HTTPException(400, "Minimum interval is 5 seconds") await _require_permission(user.id, ext_id, "ext.tasks.schedule") await _require_permission(user.id, ext_id, "ext.db.read_write") - funcs = getattr(ext, "public_wasm_functions", None) or _load_public_wasm_functions( - ext_id - ) + funcs = getattr( + ext, "public_wasm_functions", None + ) or _load_public_wasm_functions(ext_id) if handler not in funcs: raise HTTPException(400, "Handler not allowed") @@ -1088,7 +1101,7 @@ def _register_watch_routes(router: APIRouter, ext_id: str, db: Database, ext) -> await _require_permission(user.id, ext_id, "ext.db.sql") _check_quota(user.id, ext_id, "db", settings.lnbits_wasm_max_db_ops_per_min) _validate_sql(ext_id, sql, read_only=True) - rows = await db.fetchall(sql, params) # noqa: S608 + rows: list[dict] = await db.fetchall(sql, params) return {"rows": rows} @router.post("/api/v1/sql/exec") @@ -1102,11 +1115,11 @@ def _register_watch_routes(router: APIRouter, ext_id: str, db: Database, ext) -> await _require_permission(user.id, ext_id, "ext.db.sql") _check_quota(user.id, ext_id, "db", settings.lnbits_wasm_max_db_ops_per_min) _validate_sql(ext_id, sql, read_only=False) - await db.execute(sql, params) # noqa: S608 + await db.execute(sql, params) return {"ok": True} -def _start_payment_watch( +def _start_payment_watch( # noqa: C901 ext_id: str, db: Database, payment_hash: str, @@ -1118,20 +1131,24 @@ def _start_payment_watch( queue_name = f"wasm:{ext_id}:{payment_hash}:{time.time()}" invoice_queue: asyncio.Queue = asyncio.Queue() register_invoice_listener(invoice_queue, queue_name) - logger.debug(f"wasm watch registered ext={ext_id} hash={payment_hash} store_key={store_key}") + logger.debug( + f"wasm watch registered ext={ext_id} hash={payment_hash} store_key={store_key}" + ) async def _watch(): try: existing = await get_standalone_payment(payment_hash, incoming=True) if existing and existing.pending is False: - logger.debug(f"wasm watch already paid ext={ext_id} hash={payment_hash}") + logger.debug( + f"wasm watch already paid ext={ext_id} hash={payment_hash}" + ) if tag: extra = existing.extra or {} if extra.get("tag") != tag: extra["tag"] = tag existing.extra = extra existing.tag = tag - await update_payment(existing, conn=db) + await update_payment(existing) payload_json = json.dumps(existing.dict(exclude={"preimage"})) await _kv_set(db, ext_id, store_key, payload_json) await websocket_updater(f"{ext_id}:{store_key}", payload_json) @@ -1154,14 +1171,19 @@ def _start_payment_watch( payment = await invoice_queue.get() if payment.payment_hash != payment_hash: continue - logger.debug(f"wasm watch event ext={ext_id} hash={payment_hash} pending={payment.pending}") + logger.debug( + "wasm watch event ext=%s hash=%s pending=%s", + ext_id, + payment_hash, + payment.pending, + ) if tag: extra = payment.extra or {} if extra.get("tag") != tag: extra["tag"] = tag payment.extra = extra payment.tag = tag - await update_payment(payment, conn=db) + await update_payment(payment) if payment.pending is False: payload_json = json.dumps(payment.dict(exclude={"preimage"})) await _kv_set(db, ext_id, store_key, payload_json) @@ -1171,7 +1193,9 @@ def _start_payment_watch( "store_key": store_key, "tag": tag, } - await _kv_set(db, ext_id, "watch_request", json.dumps(watch_payload)) + await _kv_set( + db, ext_id, "watch_request", json.dumps(watch_payload) + ) watch_value = store_key.rsplit(":", 1)[-1] await _kv_set(db, ext_id, "public_request", watch_value) await wasm_call( @@ -1251,8 +1275,12 @@ def register_wasm_ext_routes(app, ext) -> None: prefix = f"/upgrades/{ext.upgrade_hash}" if ext.upgrade_hash else "" app.include_router(router, prefix=prefix) - asyncio.create_task(restore_tag_watches(ext_id, ext.upgrade_hash)) - asyncio.create_task(restore_schedules(ext_id, ext.upgrade_hash)) + _background_tasks.append( + asyncio.create_task(restore_tag_watches(ext_id, ext.upgrade_hash)) + ) + _background_tasks.append( + asyncio.create_task(restore_schedules(ext_id, ext.upgrade_hash)) + ) _quota_events: dict[tuple[str, str, str], list[float]] = {} diff --git a/lnbits/core/wasm/runner.py b/lnbits/core/wasm/runner.py index 277565e1e..7043faa33 100644 --- a/lnbits/core/wasm/runner.py +++ b/lnbits/core/wasm/runner.py @@ -19,8 +19,8 @@ from wasmtime import ( ValType, ) -from lnbits.db import Database from lnbits.core.services import websocket_updater +from lnbits.db import Database from lnbits.settings import settings _kv_schema_cache: dict[str, dict] = {} @@ -169,7 +169,8 @@ def _get_memory(caller: Caller): def _read_bytes(caller: Caller, ptr: int, length: int) -> bytes: memory = _get_memory(caller) - return memory.read(caller, ptr, ptr + length) + # wasmtime's memory API is dynamically typed; keep pyright quiet + return memory.read(caller, ptr, ptr + length) # type: ignore[attr-defined] def _write_bytes(caller: Caller, ptr: int, data: bytes) -> None: @@ -179,7 +180,7 @@ def _write_bytes(caller: Caller, ptr: int, data: bytes) -> None: elif isinstance(data, str): data = data.encode() try: - memory.write(caller, data, ptr) + memory.write(caller, data, ptr) # type: ignore[attr-defined] except Exception as exc: raise RuntimeError(f"memory.write failed for type={type(data)}") from exc @@ -468,6 +469,35 @@ def _load_module(module_path: Path, ext_id: str): access_caller=True, ), ) + + def _http_request_wrapper( + caller, + method_ptr, + method_len, + path_ptr, + path_len, + body_ptr, + body_len, + key_ptr, + key_len, + out_ptr, + out_len, + ): + return _http_request( + ext_id, + caller, + method_ptr, + method_len, + path_ptr, + path_len, + body_ptr, + body_len, + key_ptr, + key_len, + out_ptr, + out_len, + ) + linker_define( linker, "host", @@ -489,20 +519,7 @@ def _load_module(module_path: Path, ext_id: str): ], [ValType.i32()], ), - lambda caller, method_ptr, method_len, path_ptr, path_len, body_ptr, body_len, key_ptr, key_len, out_ptr, out_len: _http_request( - ext_id, - caller, - method_ptr, - method_len, - path_ptr, - path_len, - body_ptr, - body_len, - key_ptr, - key_len, - out_ptr, - out_len, - ), + _http_request_wrapper, access_caller=True, ), ) @@ -519,10 +536,10 @@ def _load_module(module_path: Path, ext_id: str): lambda caller, topic_ptr, topic_len, payload_ptr, payload_len: _run( _ws_publish( ext_id, - _read_bytes(caller, topic_ptr, topic_len) - .decode(errors="ignore"), - _read_bytes(caller, payload_ptr, payload_len) - .decode(errors="ignore"), + _read_bytes(caller, topic_ptr, topic_len).decode(errors="ignore"), + _read_bytes(caller, payload_ptr, payload_len).decode( + errors="ignore" + ), ) ), access_caller=True, diff --git a/lnbits/templates/pages/extensions.vue b/lnbits/templates/pages/extensions.vue index 2dbc2f5d9..caad4123d 100644 --- a/lnbits/templates/pages/extensions.vue +++ b/lnbits/templates/pages/extensions.vue @@ -956,8 +956,7 @@