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