3baf8147de327d4d68ee12d53a5733bc1666720267bbc75475bfafe89715156a.json raw

   1  {"ast":null,"code":"import _asyncToGenerator from \"/home/mleku/src/orly.dev/next/signer/node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js\";\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { backgroundLogNip07Action, backgroundLogPermissionStored, NostrHelper, NwcClient } from '@common';\nimport { checkPermissions, checkWeblnPermissions, debug, getBrowserSessionData, getPosition, handleUnlockRequest, isWeblnMethod, nip04Decrypt, nip04Encrypt, nip44Decrypt, nip44Encrypt, openUnlockPopup, shouldRecklessModeApprove, signEvent, storePermission } from './background-common';\nimport { isMlsMethod, mlsInit, mlsSendDM, mlsSubscribe, mlsPublishKP, mlsListGroups, mlsDeliverEvent, mlsSetTab } from './mls-engine';\nimport browser from 'webextension-polyfill';\nimport { Buffer } from 'buffer';\n// Clear stale session data on extension install/update/reload.\n// Session storage can survive reloads in Firefox, causing the vault\n// to appear unlocked without the user entering their password.\nbrowser.runtime.onInstalled.addListener(/*#__PURE__*/_asyncToGenerator(function* () {\n  debug('Extension installed/updated — clearing session storage');\n  yield browser.storage.session.clear();\n}));\n// Cache for NWC clients to avoid reconnecting for each request\nconst nwcClientCache = new Map();\n/**\n * Get or create an NWC client for a connection\n */\nfunction getNwcClient(_x) {\n  return _getNwcClient.apply(this, arguments);\n}\n/**\n * Parse invoice amount from a BOLT11 invoice string\n * Returns amount in satoshis, or undefined if no amount specified\n */\nfunction _getNwcClient() {\n  _getNwcClient = _asyncToGenerator(function* (connection) {\n    const cached = nwcClientCache.get(connection.id);\n    if (cached && cached.isConnected()) {\n      return cached;\n    }\n    const client = new NwcClient({\n      walletPubkey: connection.walletPubkey,\n      relayUrl: connection.relayUrl,\n      secret: connection.secret\n    });\n    yield client.connect();\n    nwcClientCache.set(connection.id, client);\n    return client;\n  });\n  return _getNwcClient.apply(this, arguments);\n}\nfunction parseInvoiceAmount(invoice) {\n  try {\n    // BOLT11 invoices start with 'ln' followed by network prefix and amount\n    // Format: ln[network][amount][multiplier]1[data]\n    // Examples: lnbc1500n1... (1500 sat), lnbc1m1... (0.001 BTC = 100000 sat)\n    const match = invoice.toLowerCase().match(/^ln(bc|tb|tbs|bcrt)(\\d+)([munp])?1/);\n    if (!match) {\n      return undefined;\n    }\n    const amountStr = match[2];\n    const multiplier = match[3];\n    let amount = parseInt(amountStr, 10);\n    // Apply multiplier (amount is in BTC by default)\n    switch (multiplier) {\n      case 'm':\n        // milli-bitcoin (0.001 BTC)\n        amount = amount * 100000;\n        break;\n      case 'u':\n        // micro-bitcoin (0.000001 BTC)\n        amount = amount * 100;\n        break;\n      case 'n':\n        // nano-bitcoin (0.000000001 BTC) = 0.1 sat\n        amount = Math.floor(amount / 10);\n        break;\n      case 'p':\n        // pico-bitcoin (0.000000000001 BTC) = 0.0001 sat\n        amount = Math.floor(amount / 10000);\n        break;\n      default:\n        // No multiplier means BTC\n        amount = amount * 100000000;\n    }\n    return amount;\n  } catch {\n    return undefined;\n  }\n}\n// ==========================================\n// Permission Prompt Queue System (P0)\n// ==========================================\n// Timeout for permission prompts (30 seconds)\nconst PROMPT_TIMEOUT_MS = 30000;\n// Maximum number of queued permission requests (prevent DoS)\nconst MAX_PERMISSION_QUEUE_SIZE = 100;\n// Track open prompts with metadata for cleanup\nconst openPrompts = new Map();\n// Track if unlock popup is already open\nlet unlockPopupOpen = false;\nlet unlockPopupWindowId;\n// Queue of pending NIP-07 requests waiting for unlock\nconst pendingRequests = [];\nconst permissionQueue = [];\nlet activePromptId = null;\n/**\n * Show the next permission prompt from the queue.\n * Re-checks permissions before opening — if a prior \"always\" response\n * already covers this request, auto-resolve it and skip to the next.\n */\nfunction showNextPermissionPrompt() {\n  return _showNextPermissionPrompt.apply(this, arguments);\n}\n/**\n * Check if a queued prompt's request is already covered by a stored permission.\n * Returns true (allowed), false (denied), or undefined (no stored permission).\n */\nfunction _showNextPermissionPrompt() {\n  _showNextPermissionPrompt = _asyncToGenerator(function* () {\n    while (!activePromptId && permissionQueue.length > 0) {\n      const next = permissionQueue[0];\n      // Re-check: a prior \"always\" may already cover this queued request.\n      const covered = yield isQueuedRequestCovered(next);\n      if (covered !== undefined) {\n        // Auto-resolve without opening a window.\n        permissionQueue.shift();\n        const promptData = openPrompts.get(next.id);\n        if (promptData) {\n          if (promptData.timeoutId) clearTimeout(promptData.timeoutId);\n          promptData.resolve(covered ? 'approve-once' : 'reject-once');\n          openPrompts.delete(next.id);\n        }\n        debug(`Auto-resolved queued prompt ${next.id} (permission already ${covered ? 'allowed' : 'denied'})`);\n        continue; // check next item\n      }\n      // No stored permission — show the prompt window.\n      activePromptId = next.id;\n      const {\n        top,\n        left\n      } = yield getPosition(next.width, next.height);\n      try {\n        const window = yield browser.windows.create({\n          type: 'popup',\n          url: next.url,\n          height: next.height,\n          width: next.width,\n          top,\n          left\n        });\n        const promptData = openPrompts.get(next.id);\n        if (promptData && window.id) {\n          promptData.windowId = window.id;\n          promptData.timeoutId = setTimeout(() => {\n            debug(`Prompt ${next.id} timed out after ${PROMPT_TIMEOUT_MS}ms`);\n            cleanupPrompt(next.id, 'timeout');\n          }, PROMPT_TIMEOUT_MS);\n        }\n      } catch (error) {\n        debug(`Failed to create prompt window: ${error}`);\n        cleanupPrompt(next.id, 'error');\n      }\n      break; // only open one prompt at a time\n    }\n  });\n  return _showNextPermissionPrompt.apply(this, arguments);\n}\nfunction isQueuedRequestCovered(_x2) {\n  return _isQueuedRequestCovered.apply(this, arguments);\n}\n/**\n * Clean up a prompt and process the next one in queue\n */\nfunction _isQueuedRequestCovered() {\n  _isQueuedRequestCovered = _asyncToGenerator(function* (item) {\n    const browserSessionData = yield getBrowserSessionData();\n    if (!browserSessionData) return undefined;\n    const currentIdentity = browserSessionData.identities.find(x => x.id === browserSessionData.selectedIdentityId);\n    if (!currentIdentity) return undefined;\n    // Parse host and method from the prompt URL query params.\n    try {\n      const url = new URL(item.url, 'http://ext');\n      const host = url.searchParams.get('host');\n      const method = url.searchParams.get('method');\n      if (!host || !method) return undefined;\n      return checkPermissions(browserSessionData, currentIdentity, host, method, {});\n    } catch {\n      return undefined;\n    }\n  });\n  return _isQueuedRequestCovered.apply(this, arguments);\n}\nfunction cleanupPrompt(promptId, reason) {\n  const promptData = openPrompts.get(promptId);\n  if (promptData) {\n    if (promptData.timeoutId) {\n      clearTimeout(promptData.timeoutId);\n    }\n    if (reason !== 'response') {\n      promptData.reject(new Error(`Permission prompt ${reason}`));\n    }\n    openPrompts.delete(promptId);\n  }\n  const queueIndex = permissionQueue.findIndex(item => item.id === promptId);\n  if (queueIndex !== -1) {\n    permissionQueue.splice(queueIndex, 1);\n  }\n  if (activePromptId === promptId) {\n    activePromptId = null;\n  }\n  showNextPermissionPrompt();\n}\n/**\n * Queue a permission prompt request\n */\nfunction queuePermissionPrompt(urlWithoutId, width, height) {\n  return new Promise((resolve, reject) => {\n    if (permissionQueue.length >= MAX_PERMISSION_QUEUE_SIZE) {\n      reject(new Error('Too many pending permission requests. Please try again later.'));\n      return;\n    }\n    const id = crypto.randomUUID();\n    const separator = urlWithoutId.includes('?') ? '&' : '?';\n    const url = `${urlWithoutId}${separator}id=${id}`;\n    openPrompts.set(id, {\n      resolve,\n      reject\n    });\n    permissionQueue.push({\n      id,\n      url,\n      width,\n      height,\n      resolve,\n      reject\n    });\n    debug(`Queued permission prompt ${id}. Queue size: ${permissionQueue.length}`);\n    showNextPermissionPrompt();\n  });\n}\n// Listen for window close events to clean up orphaned prompts and unlock popup\nbrowser.windows.onRemoved.addListener(windowId => {\n  // Handle unlock popup closed without successful unlock\n  if (unlockPopupWindowId === windowId) {\n    debug('Unlock popup closed without successful unlock');\n    unlockPopupOpen = false;\n    unlockPopupWindowId = undefined;\n    // Reject all pending requests — vault is still locked\n    while (pendingRequests.length > 0) {\n      const pending = pendingRequests.shift();\n      pending.reject(new Error('Vault unlock cancelled'));\n    }\n  }\n  for (const [promptId, promptData] of openPrompts.entries()) {\n    if (promptData.windowId === windowId) {\n      debug(`Prompt window ${windowId} closed without response`);\n      cleanupPrompt(promptId, 'closed');\n      break;\n    }\n  }\n});\n// ==========================================\n// Request Deduplication (P1)\n// ==========================================\nconst pendingRequestPromises = new Map();\n/**\n * Generate a hash key for request deduplication\n */\nfunction getRequestHash(host, method, params) {\n  if (method === 'signEvent' && params?.kind !== undefined) {\n    return `${host}:${method}:kind${params.kind}`;\n  }\n  // encrypt/decrypt permissions are blanket per host+method (no peerPubkey),\n  // so dedup must match that granularity — one prompt covers all peers.\n  return `${host}:${method}`;\n}\n/**\n * Queue a permission prompt with deduplication\n */\nfunction queuePermissionPromptDeduped(host, method, params, urlWithoutId, width, height) {\n  const hash = getRequestHash(host, method, params);\n  const existingPromise = pendingRequestPromises.get(hash);\n  if (existingPromise) {\n    debug(`Deduplicating request: ${hash}`);\n    return existingPromise;\n  }\n  const promise = queuePermissionPrompt(urlWithoutId, width, height).finally(() => {\n    pendingRequestPromises.delete(hash);\n  });\n  pendingRequestPromises.set(hash, promise);\n  debug(`New permission request: ${hash}`);\n  return promise;\n}\nbrowser.runtime.onMessage.addListener(/*#__PURE__*/function () {\n  var _ref2 = _asyncToGenerator(function* (message, sender) {\n    debug('Message received');\n    // Handle unlock request from unlock popup\n    if (message?.type === 'unlock-request') {\n      const unlockReq = message;\n      debug('Processing unlock request');\n      const result = yield handleUnlockRequest(unlockReq.password);\n      const response = {\n        type: 'unlock-response',\n        id: unlockReq.id,\n        success: result.success,\n        error: result.error\n      };\n      if (result.success) {\n        unlockPopupOpen = false;\n        unlockPopupWindowId = undefined;\n        // Process pending requests asynchronously — don't block the response\n        // to the unlock popup (Firefox may timeout the sendMessage otherwise).\n        const queued = [...pendingRequests];\n        pendingRequests.length = 0;\n        if (queued.length > 0) {\n          debug(`Scheduling ${queued.length} pending requests`);\n          setTimeout(/*#__PURE__*/_asyncToGenerator(function* () {\n            for (const pending of queued) {\n              try {\n                const pendingResult = yield processNip07Request(pending.request);\n                pending.resolve(pendingResult);\n              } catch (error) {\n                pending.reject(error);\n              }\n            }\n          }), 0);\n        }\n      }\n      return response;\n    }\n    const request = message;\n    debug(request);\n    if (request?.id) {\n      // Handle prompt response\n      const promptResponse = request;\n      const openPrompt = openPrompts.get(promptResponse.id);\n      if (!openPrompt) {\n        debug('Prompt response could not be matched (may have timed out)');\n        return;\n      }\n      openPrompt.resolve(promptResponse.response);\n      // If \"always\" (approve/reject/approve-all/reject-all), auto-resolve all\n      // queued prompts for the same host:method so they never open a window.\n      if (['approve', 'reject', 'approve-all', 'reject-all'].includes(promptResponse.response)) {\n        const answeredItem = permissionQueue.find(item => item.id === promptResponse.id);\n        if (answeredItem) {\n          try {\n            const answeredUrl = new URL(answeredItem.url, 'http://ext');\n            const answeredHost = answeredUrl.searchParams.get('host');\n            const answeredMethod = answeredUrl.searchParams.get('method');\n            if (answeredHost && answeredMethod) {\n              const autoResponse = ['approve', 'approve-all'].includes(promptResponse.response) ? 'approve-once' : 'reject-once';\n              // Drain matching items from the queue (iterate in reverse to safely splice).\n              for (let i = permissionQueue.length - 1; i >= 0; i--) {\n                const item = permissionQueue[i];\n                if (item.id === promptResponse.id) continue;\n                try {\n                  const itemUrl = new URL(item.url, 'http://ext');\n                  if (itemUrl.searchParams.get('host') === answeredHost && itemUrl.searchParams.get('method') === answeredMethod) {\n                    const pd = openPrompts.get(item.id);\n                    if (pd) {\n                      if (pd.timeoutId) clearTimeout(pd.timeoutId);\n                      pd.resolve(autoResponse);\n                      openPrompts.delete(item.id);\n                    }\n                    permissionQueue.splice(i, 1);\n                    debug(`Auto-resolved queued prompt ${item.id} via ${promptResponse.response}`);\n                  }\n                } catch {/* skip malformed */}\n              }\n            }\n          } catch {/* skip malformed */}\n        }\n      }\n      cleanupPrompt(promptResponse.id, 'response');\n      return;\n    }\n    const browserSessionData = yield getBrowserSessionData();\n    if (!browserSessionData) {\n      // Vault is locked - open unlock popup and queue the request\n      const req = request;\n      debug('Vault locked, opening unlock popup');\n      if (!unlockPopupOpen) {\n        unlockPopupOpen = true;\n        unlockPopupWindowId = yield openUnlockPopup(req.host);\n      }\n      // Queue this request to be processed after unlock\n      return new Promise((resolve, reject) => {\n        pendingRequests.push({\n          request: req,\n          resolve,\n          reject\n        });\n      });\n    }\n    // Process the request (NIP-07 or WebLN)\n    const req = request;\n    if (isWeblnMethod(req.method)) {\n      return processWeblnRequest(req);\n    }\n    const tabId = sender?.tab?.id;\n    if (isMlsMethod(req.method) && tabId !== undefined) {\n      mlsSetTab(tabId);\n    }\n    return processNip07Request(req, tabId);\n  });\n  return function (_x3, _x4) {\n    return _ref2.apply(this, arguments);\n  };\n}());\n/**\n * Process a NIP-07 request after vault is unlocked\n */\nfunction processNip07Request(_x5, _x6) {\n  return _processNip07Request.apply(this, arguments);\n}\n/**\n * Process a WebLN request after vault is unlocked\n */\nfunction _processNip07Request() {\n  _processNip07Request = _asyncToGenerator(function* (req, tabId) {\n    const browserSessionData = yield getBrowserSessionData();\n    if (!browserSessionData) {\n      throw new Error('Smesh Signer vault not unlocked by the user.');\n    }\n    const currentIdentity = browserSessionData.identities.find(x => x.id === browserSessionData.selectedIdentityId);\n    if (!currentIdentity) {\n      throw new Error('No Nostr identity available at endpoint.');\n    }\n    // Check reckless mode first\n    const recklessApprove = yield shouldRecklessModeApprove(req.host);\n    debug(`recklessApprove result: ${recklessApprove}`);\n    if (recklessApprove) {\n      debug('Request auto-approved via reckless mode.');\n    } else {\n      // Normal permission flow\n      const permissionState = checkPermissions(browserSessionData, currentIdentity, req.host, req.method, req.params);\n      debug(`permissionState result: ${permissionState}`);\n      if (permissionState === false) {\n        throw new Error('Permission denied');\n      }\n      if (permissionState === undefined) {\n        // Ask user for permission (queued + deduplicated)\n        const width = 375;\n        const height = 600;\n        const base64Event = Buffer.from(JSON.stringify(req.params ?? {}, undefined, 2)).toString('base64');\n        // Include queue info for user awareness\n        const queueSize = permissionQueue.length;\n        const promptUrl = `prompt.html?method=${req.method}&host=${req.host}&nick=${encodeURIComponent(currentIdentity.nick)}&event=${base64Event}&queue=${queueSize}`;\n        const response = yield queuePermissionPromptDeduped(req.host, req.method, req.params, promptUrl, width, height);\n        debug(response);\n        // Handle permission storage based on response type\n        if (response === 'approve' || response === 'reject') {\n          // Store permission for this specific kind (if signEvent) or method\n          const policy = response === 'approve' ? 'allow' : 'deny';\n          yield storePermission(browserSessionData, currentIdentity, req.host, req.method, policy, req.params?.kind);\n          yield backgroundLogPermissionStored(req.host, req.method, policy, req.params?.kind);\n        } else if (response === 'approve-all') {\n          // P2: Store permission for ALL kinds/uses of this method from this host\n          yield storePermission(browserSessionData, currentIdentity, req.host, req.method, 'allow', undefined // undefined kind = allow all kinds for signEvent\n          );\n          yield backgroundLogPermissionStored(req.host, req.method, 'allow', undefined);\n          debug(`Stored approve-all permission for ${req.method} from ${req.host}`);\n        } else if (response === 'reject-all') {\n          // P2: Store deny permission for ALL uses of this method from this host\n          yield storePermission(browserSessionData, currentIdentity, req.host, req.method, 'deny', undefined);\n          yield backgroundLogPermissionStored(req.host, req.method, 'deny', undefined);\n          debug(`Stored reject-all permission for ${req.method} from ${req.host}`);\n        }\n        if (['reject', 'reject-once', 'reject-all'].includes(response)) {\n          yield backgroundLogNip07Action(req.method, req.host, false, false, {\n            kind: req.params?.kind,\n            peerPubkey: req.params?.peerPubkey\n          });\n          throw new Error('Permission denied');\n        }\n      } else {\n        debug('Request allowed (via saved permission).');\n      }\n    }\n    const relays = {};\n    let result;\n    switch (req.method) {\n      case 'getPublicKey':\n        result = NostrHelper.pubkeyFromPrivkey(currentIdentity.privkey);\n        yield backgroundLogNip07Action(req.method, req.host, true, recklessApprove);\n        return result;\n      case 'signEvent':\n        result = signEvent(req.params, currentIdentity.privkey);\n        yield backgroundLogNip07Action(req.method, req.host, true, recklessApprove, {\n          kind: req.params?.kind\n        });\n        return result;\n      case 'getRelays':\n        browserSessionData.relays.forEach(x => {\n          relays[x.url] = {\n            read: x.read,\n            write: x.write\n          };\n        });\n        yield backgroundLogNip07Action(req.method, req.host, true, recklessApprove);\n        return relays;\n      case 'nip04.encrypt':\n        result = yield nip04Encrypt(currentIdentity.privkey, req.params.peerPubkey, req.params.plaintext);\n        yield backgroundLogNip07Action(req.method, req.host, true, recklessApprove, {\n          peerPubkey: req.params.peerPubkey\n        });\n        return result;\n      case 'nip44.encrypt':\n        result = yield nip44Encrypt(currentIdentity.privkey, req.params.peerPubkey, req.params.plaintext);\n        yield backgroundLogNip07Action(req.method, req.host, true, recklessApprove, {\n          peerPubkey: req.params.peerPubkey\n        });\n        return result;\n      case 'nip04.decrypt':\n        result = yield nip04Decrypt(currentIdentity.privkey, req.params.peerPubkey, req.params.ciphertext);\n        yield backgroundLogNip07Action(req.method, req.host, true, recklessApprove, {\n          peerPubkey: req.params.peerPubkey\n        });\n        return result;\n      case 'nip44.decrypt':\n        result = yield nip44Decrypt(currentIdentity.privkey, req.params.peerPubkey, req.params.ciphertext);\n        yield backgroundLogNip07Action(req.method, req.host, true, recklessApprove, {\n          peerPubkey: req.params.peerPubkey\n        });\n        return result;\n      // MLS operations — all crypto happens locally in the extension\n      case 'mls.init':\n        if (tabId === undefined) throw new Error('MLS requires tab context');\n        result = yield mlsInit(currentIdentity.privkey, NostrHelper.pubkeyFromPrivkey(currentIdentity.privkey), req.params.relayURLs || [], tabId);\n        return result;\n      case 'mls.sendDM':\n        return mlsSendDM(req.params.recipient, req.params.content);\n      case 'mls.subscribe':\n        return mlsSubscribe();\n      case 'mls.publishKP':\n        return mlsPublishKP();\n      case 'mls.listGroups':\n        return JSON.parse(mlsListGroups());\n      case 'mls.deliverEvent':\n        mlsDeliverEvent(req.params.subId, req.params.eventJSON);\n        return 'ok';\n      default:\n        throw new Error(`Not supported request method '${req.method}'.`);\n    }\n  });\n  return _processNip07Request.apply(this, arguments);\n}\nfunction processWeblnRequest(_x7) {\n  return _processWeblnRequest.apply(this, arguments);\n}\nfunction _processWeblnRequest() {\n  _processWeblnRequest = _asyncToGenerator(function* (req) {\n    const browserSessionData = yield getBrowserSessionData();\n    if (!browserSessionData) {\n      throw new Error('Smesh Signer vault not unlocked by the user.');\n    }\n    const nwcConnections = browserSessionData.nwcConnections ?? [];\n    const method = req.method;\n    // webln.enable just checks if NWC is configured\n    if (method === 'webln.enable') {\n      if (nwcConnections.length === 0) {\n        throw new Error('No wallet configured. Please add an NWC connection in Smesh Signer settings.');\n      }\n      debug('WebLN enabled');\n      return {\n        enabled: true\n      }; // Return explicit value (undefined gets filtered by content script)\n    }\n    // All other methods require an NWC connection\n    const defaultConnection = nwcConnections[0];\n    if (!defaultConnection) {\n      throw new Error('No wallet configured. Please add an NWC connection in Smesh Signer settings.');\n    }\n    // Check reckless mode (but still prompt for payments)\n    const recklessApprove = yield shouldRecklessModeApprove(req.host);\n    // Check WebLN permissions\n    const permissionState = recklessApprove && method !== 'webln.sendPayment' && method !== 'webln.keysend' ? true : checkWeblnPermissions(browserSessionData, req.host, method);\n    if (permissionState === false) {\n      throw new Error('Permission denied');\n    }\n    if (permissionState === undefined) {\n      // Ask user for permission (queued + deduplicated)\n      const width = 375;\n      const height = 600;\n      // For sendPayment, include the invoice amount in the prompt data\n      let promptParams = req.params ?? {};\n      if (method === 'webln.sendPayment' && req.params?.paymentRequest) {\n        const amountSats = parseInvoiceAmount(req.params.paymentRequest);\n        promptParams = {\n          ...promptParams,\n          amountSats\n        };\n      }\n      const base64Event = Buffer.from(JSON.stringify(promptParams, undefined, 2)).toString('base64');\n      // Include queue info for user awareness\n      const queueSize = permissionQueue.length;\n      const promptUrl = `prompt.html?method=${method}&host=${req.host}&nick=WebLN&event=${base64Event}&queue=${queueSize}`;\n      const response = yield queuePermissionPromptDeduped(req.host, method, req.params, promptUrl, width, height);\n      debug(response);\n      // Store permission for non-payment methods\n      if ((response === 'approve' || response === 'reject') && method !== 'webln.sendPayment' && method !== 'webln.keysend') {\n        const policy = response === 'approve' ? 'allow' : 'deny';\n        yield storePermission(browserSessionData, null,\n        // WebLN has no identity\n        req.host, method, policy);\n        yield backgroundLogPermissionStored(req.host, method, policy);\n      } else if (response === 'approve-all' && method !== 'webln.sendPayment' && method !== 'webln.keysend') {\n        // P2: Store permission for all uses of this WebLN method\n        yield storePermission(browserSessionData, null, req.host, method, 'allow');\n        yield backgroundLogPermissionStored(req.host, method, 'allow');\n        debug(`Stored approve-all permission for ${method} from ${req.host}`);\n      }\n      if (['reject', 'reject-once', 'reject-all'].includes(response)) {\n        throw new Error('Permission denied');\n      }\n    }\n    // Execute the WebLN method\n    let result;\n    const client = yield getNwcClient(defaultConnection);\n    switch (method) {\n      case 'webln.getInfo':\n        {\n          const info = yield client.getInfo();\n          result = {\n            node: {\n              alias: info.alias,\n              pubkey: info.pubkey,\n              color: info.color\n            }\n          };\n          debug('webln.getInfo result:');\n          debug(result);\n          return result;\n        }\n      case 'webln.sendPayment':\n        {\n          const invoice = req.params.paymentRequest;\n          const payResult = yield client.payInvoice({\n            invoice\n          });\n          result = {\n            preimage: payResult.preimage\n          };\n          debug('webln.sendPayment result:');\n          debug(result);\n          return result;\n        }\n      case 'webln.makeInvoice':\n        {\n          // Convert sats to millisats (NWC uses millisats)\n          const amountSats = typeof req.params.amount === 'string' ? parseInt(req.params.amount, 10) : req.params.amount ?? req.params.defaultAmount ?? 0;\n          const amountMsat = amountSats * 1000;\n          const invoiceResult = yield client.makeInvoice({\n            amount: amountMsat,\n            description: req.params.defaultMemo\n          });\n          result = {\n            paymentRequest: invoiceResult.invoice\n          };\n          debug('webln.makeInvoice result:');\n          debug(result);\n          return result;\n        }\n      case 'webln.keysend':\n        throw new Error('keysend is not yet supported');\n      default:\n        throw new Error(`Not supported WebLN method '${method}'.`);\n    }\n  });\n  return _processWeblnRequest.apply(this, arguments);\n}","map":null,"metadata":{},"sourceType":"module","externalDependencies":[]}