fix: production build
This commit is contained in:
@@ -1,273 +1,273 @@
|
||||
import browser from "webextension-polyfill";
|
||||
import browser from 'webextension-polyfill'
|
||||
import {
|
||||
validateEvent,
|
||||
finalizeEvent,
|
||||
getEventHash,
|
||||
getPublicKey,
|
||||
nip19,
|
||||
utils,
|
||||
} from "nostr-tools";
|
||||
import { nip04 } from "nostr-tools";
|
||||
import * as nip44 from "nostr-tools/nip44";
|
||||
import { Mutex } from "async-mutex";
|
||||
import { LRUCache } from "./utils";
|
||||
utils
|
||||
} from 'nostr-tools'
|
||||
import { nip04 } from 'nostr-tools'
|
||||
import * as nip44 from 'nostr-tools/nip44'
|
||||
import { Mutex } from 'async-mutex'
|
||||
import { LRUCache } from './utils'
|
||||
|
||||
const { hexToBytes } = utils;
|
||||
const { hexToBytes } = utils
|
||||
|
||||
import {
|
||||
NO_PERMISSIONS_REQUIRED,
|
||||
getPermissionStatus,
|
||||
updatePermission,
|
||||
showNotification,
|
||||
getPosition,
|
||||
} from "./common";
|
||||
getPosition
|
||||
} from './common'
|
||||
|
||||
const { encrypt, decrypt } = nip04;
|
||||
const { encrypt, decrypt } = nip04
|
||||
|
||||
let openPrompt = null;
|
||||
const promptMutex = new Mutex();
|
||||
let releasePromptMutex = () => {};
|
||||
const secretsCache = new LRUCache(100);
|
||||
const previousSk = null;
|
||||
let openPrompt = null
|
||||
const promptMutex = new Mutex()
|
||||
let releasePromptMutex = () => {}
|
||||
const secretsCache = new LRUCache(100)
|
||||
const previousSk = null
|
||||
|
||||
function getSharedSecret(sk, peer) {
|
||||
if (previousSk !== sk) {
|
||||
secretsCache.clear();
|
||||
secretsCache.clear()
|
||||
}
|
||||
|
||||
let key = secretsCache.get(peer);
|
||||
let key = secretsCache.get(peer)
|
||||
|
||||
if (!key) {
|
||||
key = nip44.v2.utils.getConversationKey(sk, peer);
|
||||
secretsCache.set(peer, key);
|
||||
key = nip44.v2.utils.getConversationKey(sk, peer)
|
||||
secretsCache.set(peer, key)
|
||||
}
|
||||
|
||||
return key;
|
||||
return key
|
||||
}
|
||||
|
||||
const width = 440;
|
||||
const height = 420;
|
||||
const width = 440
|
||||
const height = 420
|
||||
|
||||
browser.runtime.onInstalled.addListener((_, __, reason) => {
|
||||
if (reason === "install") browser.runtime.openOptionsPage();
|
||||
});
|
||||
if (reason === 'install') browser.runtime.openOptionsPage()
|
||||
})
|
||||
|
||||
browser.runtime.onMessage.addListener(async (req, sender) => {
|
||||
const { prompt } = req;
|
||||
const { prompt } = req
|
||||
|
||||
if (prompt) {
|
||||
handlePromptMessage(req, sender);
|
||||
handlePromptMessage(req, sender)
|
||||
} else {
|
||||
return handleContentScriptMessage(req);
|
||||
return handleContentScriptMessage(req)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
browser.runtime.onMessageExternal.addListener(
|
||||
async ({ type, params }, sender) => {
|
||||
const extensionId = new URL(sender.url).host;
|
||||
return handleContentScriptMessage({ type, params, host: extensionId });
|
||||
const extensionId = new URL(sender.url).host
|
||||
return handleContentScriptMessage({ type, params, host: extensionId })
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
browser.windows.onRemoved.addListener((_windowId) => {
|
||||
if (openPrompt) {
|
||||
// calling this with a simple "no" response will not store anything, so it's fine
|
||||
// it will just return a failure
|
||||
handlePromptMessage({ accept: false }, null);
|
||||
handlePromptMessage({ accept: false }, null)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
async function handleContentScriptMessage({ type, params, host }) {
|
||||
if (NO_PERMISSIONS_REQUIRED[type]) {
|
||||
switch (type) {
|
||||
case "peekPublicKey": {
|
||||
const allowed = await getPermissionStatus(host, "getPublicKey");
|
||||
if (allowed === true) return performOperation("getPublicKey", params);
|
||||
return "";
|
||||
case 'peekPublicKey': {
|
||||
const allowed = await getPermissionStatus(host, 'getPublicKey')
|
||||
if (allowed === true) return performOperation('getPublicKey', params)
|
||||
return ''
|
||||
}
|
||||
case "replaceURL": {
|
||||
case 'replaceURL': {
|
||||
const { protocol_handler: ph } = await browser.storage.local.get([
|
||||
"protocol_handler",
|
||||
]);
|
||||
if (!ph) return false;
|
||||
'protocol_handler'
|
||||
])
|
||||
if (!ph) return false
|
||||
|
||||
const { url } = params;
|
||||
const raw = url.split("nostr:")[1];
|
||||
const { type, data } = nip19.decode(raw);
|
||||
const { url } = params
|
||||
const raw = url.split('nostr:')[1]
|
||||
const { type, data } = nip19.decode(raw)
|
||||
const replacements = {
|
||||
raw,
|
||||
hrp: type,
|
||||
hex:
|
||||
type === "npub" || type === "note"
|
||||
type === 'npub' || type === 'note'
|
||||
? data
|
||||
: type === "nprofile"
|
||||
? data.pubkey
|
||||
: type === "nevent"
|
||||
? data.id
|
||||
: null,
|
||||
p_or_e: { npub: "p", note: "e", nprofile: "p", nevent: "e" }[type],
|
||||
u_or_n: { npub: "u", note: "n", nprofile: "u", nevent: "n" }[type],
|
||||
relay0: type === "nprofile" ? data.relays[0] : null,
|
||||
relay1: type === "nprofile" ? data.relays[1] : null,
|
||||
relay2: type === "nprofile" ? data.relays[2] : null,
|
||||
};
|
||||
let result = ph;
|
||||
: type === 'nprofile'
|
||||
? data.pubkey
|
||||
: type === 'nevent'
|
||||
? data.id
|
||||
: null,
|
||||
p_or_e: { npub: 'p', note: 'e', nprofile: 'p', nevent: 'e' }[type],
|
||||
u_or_n: { npub: 'u', note: 'n', nprofile: 'u', nevent: 'n' }[type],
|
||||
relay0: type === 'nprofile' ? data.relays[0] : null,
|
||||
relay1: type === 'nprofile' ? data.relays[1] : null,
|
||||
relay2: type === 'nprofile' ? data.relays[2] : null
|
||||
}
|
||||
let result = ph
|
||||
Object.entries(replacements).forEach(([pattern, value]) => {
|
||||
result = result.replace(new RegExp(`{ *${pattern} *}`, "g"), value);
|
||||
});
|
||||
result = result.replace(new RegExp(`{ *${pattern} *}`, 'g'), value)
|
||||
})
|
||||
|
||||
return result;
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
return
|
||||
} else {
|
||||
// acquire mutex here before reading policies
|
||||
releasePromptMutex = await promptMutex.acquire();
|
||||
releasePromptMutex = await promptMutex.acquire()
|
||||
|
||||
const allowed = await getPermissionStatus(
|
||||
host,
|
||||
type,
|
||||
type === "signEvent" ? params.event : undefined
|
||||
);
|
||||
type === 'signEvent' ? params.event : undefined
|
||||
)
|
||||
|
||||
if (allowed === true) {
|
||||
// authorized, proceed
|
||||
releasePromptMutex();
|
||||
showNotification(host, allowed, type, params);
|
||||
releasePromptMutex()
|
||||
showNotification(host, allowed, type, params)
|
||||
} else if (allowed === false) {
|
||||
// denied, just refuse immediately
|
||||
releasePromptMutex();
|
||||
showNotification(host, allowed, type, params);
|
||||
releasePromptMutex()
|
||||
showNotification(host, allowed, type, params)
|
||||
return {
|
||||
error: "denied",
|
||||
};
|
||||
error: 'denied'
|
||||
}
|
||||
} else {
|
||||
// ask for authorization
|
||||
try {
|
||||
const id = Math.random().toString().slice(4);
|
||||
const id = Math.random().toString().slice(4)
|
||||
const qs = new URLSearchParams({
|
||||
host,
|
||||
id,
|
||||
params: JSON.stringify(params),
|
||||
type,
|
||||
});
|
||||
type
|
||||
})
|
||||
|
||||
// prompt will be resolved with true or false
|
||||
const accept = await new Promise((resolve, reject) => {
|
||||
openPrompt = { resolve, reject };
|
||||
openPrompt = { resolve, reject }
|
||||
const url = `${browser.runtime.getURL(
|
||||
"prompt.html"
|
||||
)}?${qs.toString()}`;
|
||||
'prompt.html'
|
||||
)}?${qs.toString()}`
|
||||
|
||||
// center prompt
|
||||
const { top, left } = getPosition(width, height);
|
||||
const { top, left } = getPosition(width, height)
|
||||
|
||||
if (browser.windows) {
|
||||
browser.windows.create({
|
||||
url,
|
||||
type: "popup",
|
||||
type: 'popup',
|
||||
width: width,
|
||||
height: height,
|
||||
top: top,
|
||||
left: left,
|
||||
});
|
||||
left: left
|
||||
})
|
||||
} else {
|
||||
browser.tabs.create({
|
||||
url,
|
||||
active: true,
|
||||
});
|
||||
active: true
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// denied, stop here
|
||||
if (!accept) return { error: { message: "denied" } };
|
||||
if (!accept) return { error: { message: 'denied' } }
|
||||
} catch (err) {
|
||||
// errored, stop here
|
||||
releasePromptMutex();
|
||||
releasePromptMutex()
|
||||
return {
|
||||
error: { message: err.message, stack: err.stack },
|
||||
};
|
||||
error: { message: err.message, stack: err.stack }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we're here this means it was accepted
|
||||
const results = await browser.storage.local.get("private_key");
|
||||
const results = await browser.storage.local.get('private_key')
|
||||
if (!results?.private_key) {
|
||||
return { error: "no private key found" };
|
||||
return { error: 'no private key found' }
|
||||
}
|
||||
|
||||
const sk = results.private_key;
|
||||
const sk = results.private_key
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case "getPublicKey": {
|
||||
return getPublicKey(hexToBytes(sk));
|
||||
case 'getPublicKey': {
|
||||
return getPublicKey(hexToBytes(sk))
|
||||
}
|
||||
case "getRelays": {
|
||||
const results = await browser.storage.local.get("relays");
|
||||
return results.relays || {};
|
||||
case 'getRelays': {
|
||||
const results = await browser.storage.local.get('relays')
|
||||
return results.relays || {}
|
||||
}
|
||||
case "signEvent": {
|
||||
const { event } = params;
|
||||
case 'signEvent': {
|
||||
const { event } = params
|
||||
|
||||
if (!event.pubkey) event.pubkey = getPublicKey(hexToBytes(sk));
|
||||
if (!event.id) event.id = getEventHash(event);
|
||||
if (!event.pubkey) event.pubkey = getPublicKey(hexToBytes(sk))
|
||||
if (!event.id) event.id = getEventHash(event)
|
||||
if (!validateEvent(event))
|
||||
return { error: { message: "invalid event" } };
|
||||
return { error: { message: 'invalid event' } }
|
||||
|
||||
const signedEvent = finalizeEvent(event, hexToBytes(sk));
|
||||
return signedEvent;
|
||||
const signedEvent = finalizeEvent(event, hexToBytes(sk))
|
||||
return signedEvent
|
||||
}
|
||||
case "nip04.encrypt": {
|
||||
const { peer, plaintext } = params;
|
||||
return encrypt(sk, peer, plaintext);
|
||||
case 'nip04.encrypt': {
|
||||
const { peer, plaintext } = params
|
||||
return encrypt(sk, peer, plaintext)
|
||||
}
|
||||
case "nip04.decrypt": {
|
||||
const { peer, ciphertext } = params;
|
||||
return decrypt(sk, peer, ciphertext);
|
||||
case 'nip04.decrypt': {
|
||||
const { peer, ciphertext } = params
|
||||
return decrypt(sk, peer, ciphertext)
|
||||
}
|
||||
case "nip44.encrypt": {
|
||||
const { peer, plaintext } = params;
|
||||
const key = getSharedSecret(sk, peer);
|
||||
case 'nip44.encrypt': {
|
||||
const { peer, plaintext } = params
|
||||
const key = getSharedSecret(sk, peer)
|
||||
|
||||
return nip44.v2.encrypt(plaintext, key);
|
||||
return nip44.v2.encrypt(plaintext, key)
|
||||
}
|
||||
case "nip44.decrypt": {
|
||||
const { peer, ciphertext } = params;
|
||||
const key = getSharedSecret(sk, peer);
|
||||
case 'nip44.decrypt': {
|
||||
const { peer, ciphertext } = params
|
||||
const key = getSharedSecret(sk, peer)
|
||||
|
||||
return nip44.v2.decrypt(ciphertext, key);
|
||||
return nip44.v2.decrypt(ciphertext, key)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return { error: { message: error.message, stack: error.stack } };
|
||||
return { error: { message: error.message, stack: error.stack } }
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePromptMessage({ host, type, accept, conditions }, sender) {
|
||||
// return response
|
||||
openPrompt?.resolve?.(accept);
|
||||
openPrompt?.resolve?.(accept)
|
||||
|
||||
// update policies
|
||||
if (conditions) {
|
||||
await updatePermission(host, type, accept, conditions);
|
||||
await updatePermission(host, type, accept, conditions)
|
||||
}
|
||||
|
||||
// cleanup this
|
||||
openPrompt = null;
|
||||
openPrompt = null
|
||||
|
||||
// release mutex here after updating policies
|
||||
releasePromptMutex();
|
||||
releasePromptMutex()
|
||||
|
||||
// close prompt
|
||||
if (sender) {
|
||||
if (browser.windows) {
|
||||
browser.windows.remove(sender.tab.windowId);
|
||||
browser.windows.remove(sender.tab.windowId)
|
||||
} else {
|
||||
// Android Firefox
|
||||
browser.tabs.remove(sender.tab.id);
|
||||
browser.tabs.remove(sender.tab.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user