prompt.ts raw

   1  import browser from 'webextension-polyfill';
   2  import { ExtensionMethod } from '@common';
   3  import { PromptResponse, PromptResponseMessage } from './background-common';
   4  
   5  /**
   6   * Decode base64 string to UTF-8 using native browser APIs.
   7   * This avoids race conditions with the Buffer polyfill initialization.
   8   */
   9  function base64ToUtf8(base64: string): string {
  10    const binaryString = atob(base64);
  11    const bytes = Uint8Array.from(binaryString, char => char.charCodeAt(0));
  12    return new TextDecoder('utf-8').decode(bytes);
  13  }
  14  
  15  const params = new URLSearchParams(location.search);
  16  const id = params.get('id') as string;
  17  const method = params.get('method') as ExtensionMethod;
  18  const host = params.get('host') as string;
  19  const nick = params.get('nick') as string;
  20  
  21  let event = '{}';
  22  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  23  let eventParsed: any = {};
  24  try {
  25    event = base64ToUtf8(params.get('event') as string);
  26    eventParsed = JSON.parse(event);
  27  } catch (e) {
  28    console.error('Failed to parse event:', e);
  29  }
  30  
  31  let title = '';
  32  switch (method) {
  33    case 'getPublicKey':
  34      title = 'Get Public Key';
  35      break;
  36  
  37    case 'signEvent':
  38      title = 'Sign Event';
  39      break;
  40  
  41    case 'nip04.encrypt':
  42      title = 'Encrypt';
  43      break;
  44  
  45    case 'nip44.encrypt':
  46      title = 'Encrypt';
  47      break;
  48  
  49    case 'nip04.decrypt':
  50      title = 'Decrypt';
  51      break;
  52  
  53    case 'nip44.decrypt':
  54      title = 'Decrypt';
  55      break;
  56  
  57    case 'getRelays':
  58      title = 'Get Relays';
  59      break;
  60  
  61    case 'webln.enable':
  62      title = 'Enable WebLN';
  63      break;
  64  
  65    case 'webln.getInfo':
  66      title = 'Wallet Info';
  67      break;
  68  
  69    case 'webln.sendPayment':
  70      title = 'Send Payment';
  71      break;
  72  
  73    case 'webln.makeInvoice':
  74      title = 'Create Invoice';
  75      break;
  76  
  77    case 'webln.keysend':
  78      title = 'Keysend Payment';
  79      break;
  80  
  81    case 'mls.*':
  82      title = 'Use Encrypted Messaging (MLS)';
  83      break;
  84  
  85    default:
  86      break;
  87  }
  88  
  89  const titleSpanElement = document.getElementById('titleSpan');
  90  if (titleSpanElement) {
  91    titleSpanElement.innerText = title;
  92  }
  93  
  94  Array.from(document.getElementsByClassName('nick-INSERT')).forEach(
  95    (element) => {
  96      (element as HTMLElement).innerText = nick;
  97    }
  98  );
  99  
 100  Array.from(document.getElementsByClassName('host-INSERT')).forEach(
 101    (element) => {
 102      (element as HTMLElement).innerText = host;
 103    }
 104  );
 105  
 106  const kindSpanElement = document.getElementById('kindSpan');
 107  if (kindSpanElement && eventParsed.kind !== undefined) {
 108    kindSpanElement.innerText = eventParsed.kind;
 109  }
 110  
 111  const cardGetPublicKeyElement = document.getElementById('cardGetPublicKey');
 112  if (cardGetPublicKeyElement) {
 113    if (method === 'getPublicKey') {
 114      // Do nothing.
 115    } else {
 116      cardGetPublicKeyElement.style.display = 'none';
 117    }
 118  }
 119  
 120  const cardGetRelaysElement = document.getElementById('cardGetRelays');
 121  if (cardGetRelaysElement) {
 122    if (method === 'getRelays') {
 123      // Do nothing.
 124    } else {
 125      cardGetRelaysElement.style.display = 'none';
 126    }
 127  }
 128  
 129  const cardSignEventElement = document.getElementById('cardSignEvent');
 130  const card2SignEventElement = document.getElementById('card2SignEvent');
 131  if (cardSignEventElement && card2SignEventElement) {
 132    if (method === 'signEvent') {
 133      const card2SignEvent_jsonElement = document.getElementById(
 134        'card2SignEvent_json'
 135      );
 136      if (card2SignEvent_jsonElement) {
 137        card2SignEvent_jsonElement.innerText = event;
 138      }
 139    } else {
 140      cardSignEventElement.style.display = 'none';
 141      card2SignEventElement.style.display = 'none';
 142    }
 143  }
 144  
 145  const cardNip04EncryptElement = document.getElementById('cardNip04Encrypt');
 146  const card2Nip04EncryptElement = document.getElementById('card2Nip04Encrypt');
 147  if (cardNip04EncryptElement && card2Nip04EncryptElement) {
 148    if (method === 'nip04.encrypt') {
 149      const card2Nip04Encrypt_textElement = document.getElementById(
 150        'card2Nip04Encrypt_text'
 151      );
 152      if (card2Nip04Encrypt_textElement) {
 153        const eventObject = eventParsed as { peerPubkey: string; plaintext: string };
 154        card2Nip04Encrypt_textElement.innerText = eventObject.plaintext || '';
 155      }
 156    } else {
 157      cardNip04EncryptElement.style.display = 'none';
 158      card2Nip04EncryptElement.style.display = 'none';
 159    }
 160  }
 161  
 162  const cardNip44EncryptElement = document.getElementById('cardNip44Encrypt');
 163  const card2Nip44EncryptElement = document.getElementById('card2Nip44Encrypt');
 164  if (cardNip44EncryptElement && card2Nip44EncryptElement) {
 165    if (method === 'nip44.encrypt') {
 166      const card2Nip44Encrypt_textElement = document.getElementById(
 167        'card2Nip44Encrypt_text'
 168      );
 169      if (card2Nip44Encrypt_textElement) {
 170        const eventObject = eventParsed as { peerPubkey: string; plaintext: string };
 171        card2Nip44Encrypt_textElement.innerText = eventObject.plaintext || '';
 172      }
 173    } else {
 174      cardNip44EncryptElement.style.display = 'none';
 175      card2Nip44EncryptElement.style.display = 'none';
 176    }
 177  }
 178  
 179  const cardNip04DecryptElement = document.getElementById('cardNip04Decrypt');
 180  const card2Nip04DecryptElement = document.getElementById('card2Nip04Decrypt');
 181  if (cardNip04DecryptElement && card2Nip04DecryptElement) {
 182    if (method === 'nip04.decrypt') {
 183      const card2Nip04Decrypt_textElement = document.getElementById(
 184        'card2Nip04Decrypt_text'
 185      );
 186      if (card2Nip04Decrypt_textElement) {
 187        const eventObject = eventParsed as { peerPubkey: string; ciphertext: string };
 188        card2Nip04Decrypt_textElement.innerText = eventObject.ciphertext || '';
 189      }
 190    } else {
 191      cardNip04DecryptElement.style.display = 'none';
 192      card2Nip04DecryptElement.style.display = 'none';
 193    }
 194  }
 195  
 196  const cardNip44DecryptElement = document.getElementById('cardNip44Decrypt');
 197  const card2Nip44DecryptElement = document.getElementById('card2Nip44Decrypt');
 198  if (cardNip44DecryptElement && card2Nip44DecryptElement) {
 199    if (method === 'nip44.decrypt') {
 200      const card2Nip44Decrypt_textElement = document.getElementById(
 201        'card2Nip44Decrypt_text'
 202      );
 203      if (card2Nip44Decrypt_textElement) {
 204        const eventObject = eventParsed as { peerPubkey: string; ciphertext: string };
 205        card2Nip44Decrypt_textElement.innerText = eventObject.ciphertext || '';
 206      }
 207    } else {
 208      cardNip44DecryptElement.style.display = 'none';
 209      card2Nip44DecryptElement.style.display = 'none';
 210    }
 211  }
 212  
 213  // WebLN card visibility
 214  const cardWeblnEnableElement = document.getElementById('cardWeblnEnable');
 215  if (cardWeblnEnableElement) {
 216    if (method !== 'webln.enable') {
 217      cardWeblnEnableElement.style.display = 'none';
 218    }
 219  }
 220  
 221  const cardWeblnGetInfoElement = document.getElementById('cardWeblnGetInfo');
 222  if (cardWeblnGetInfoElement) {
 223    if (method !== 'webln.getInfo') {
 224      cardWeblnGetInfoElement.style.display = 'none';
 225    }
 226  }
 227  
 228  const cardWeblnSendPaymentElement = document.getElementById('cardWeblnSendPayment');
 229  const card2WeblnSendPaymentElement = document.getElementById('card2WeblnSendPayment');
 230  if (cardWeblnSendPaymentElement && card2WeblnSendPaymentElement) {
 231    if (method === 'webln.sendPayment') {
 232      // Display amount in sats
 233      const paymentAmountSpan = document.getElementById('paymentAmountSpan');
 234      if (paymentAmountSpan && eventParsed.amountSats !== undefined) {
 235        paymentAmountSpan.innerText = `${eventParsed.amountSats.toLocaleString()} sats`;
 236      } else if (paymentAmountSpan) {
 237        paymentAmountSpan.innerText = 'unknown amount';
 238      }
 239      // Show invoice in json card
 240      const card2WeblnSendPayment_jsonElement = document.getElementById('card2WeblnSendPayment_json');
 241      if (card2WeblnSendPayment_jsonElement && eventParsed.paymentRequest) {
 242        card2WeblnSendPayment_jsonElement.innerText = eventParsed.paymentRequest;
 243      }
 244    } else {
 245      cardWeblnSendPaymentElement.style.display = 'none';
 246      card2WeblnSendPaymentElement.style.display = 'none';
 247    }
 248  }
 249  
 250  const cardWeblnMakeInvoiceElement = document.getElementById('cardWeblnMakeInvoice');
 251  if (cardWeblnMakeInvoiceElement) {
 252    if (method === 'webln.makeInvoice') {
 253      const invoiceAmountSpan = document.getElementById('invoiceAmountSpan');
 254      if (invoiceAmountSpan) {
 255        const amount = eventParsed.amount ?? eventParsed.defaultAmount;
 256        if (amount) {
 257          invoiceAmountSpan.innerText = ` for ${Number(amount).toLocaleString()} sats`;
 258        }
 259      }
 260    } else {
 261      cardWeblnMakeInvoiceElement.style.display = 'none';
 262    }
 263  }
 264  
 265  const cardWeblnKeysendElement = document.getElementById('cardWeblnKeysend');
 266  if (cardWeblnKeysendElement) {
 267    if (method !== 'webln.keysend') {
 268      cardWeblnKeysendElement.style.display = 'none';
 269    }
 270  }
 271  
 272  const cardMlsElement = document.getElementById('cardMls');
 273  if (cardMlsElement) {
 274    if (method === 'mls.*') {
 275      const titleSpanMls = document.getElementById('titleSpanMls');
 276      if (titleSpanMls) {
 277        titleSpanMls.innerText = title.toLowerCase();
 278      }
 279    } else {
 280      cardMlsElement.style.display = 'none';
 281    }
 282  }
 283  
 284  //
 285  // Functions
 286  //
 287  
 288  async function deliver(response: PromptResponse) {
 289    const message: PromptResponseMessage = {
 290      id,
 291      response,
 292    };
 293  
 294    try {
 295      await browser.runtime.sendMessage(message);
 296    } catch (error) {
 297      console.error('Failed to send message:', error);
 298    }
 299    window.close();
 300  }
 301  
 302  document.addEventListener('DOMContentLoaded', function () {
 303    const rejectOnceButton = document.getElementById('rejectOnceButton');
 304    rejectOnceButton?.addEventListener('click', () => {
 305      deliver('reject-once');
 306    });
 307  
 308    const rejectAlwaysButton = document.getElementById('rejectAlwaysButton');
 309    rejectAlwaysButton?.addEventListener('click', () => {
 310      deliver('reject');
 311    });
 312  
 313    const approveOnceButton = document.getElementById('approveOnceButton');
 314    approveOnceButton?.addEventListener('click', () => {
 315      deliver('approve-once');
 316    });
 317  
 318    const approveAlwaysButton = document.getElementById('approveAlwaysButton');
 319    approveAlwaysButton?.addEventListener('click', () => {
 320      deliver('approve');
 321    });
 322  
 323    const rejectAllButton = document.getElementById('rejectAllButton');
 324    rejectAllButton?.addEventListener('click', () => {
 325      deliver('reject-all');
 326    });
 327  
 328    const approveAllButton = document.getElementById('approveAllButton');
 329    approveAllButton?.addEventListener('click', () => {
 330      deliver('approve-all');
 331    });
 332  
 333    // Show/hide "All Queued" row based on queue size
 334    const queueSize = parseInt(params.get('queueSize') || '0', 10);
 335    const allQueuedRow = document.getElementById('allQueuedRow');
 336    if (allQueuedRow && queueSize <= 1) {
 337      allQueuedRow.style.display = 'none';
 338    }
 339  });
 340