From db9e0b43e5fb5fba219546efe7c8b6f04acdb24b Mon Sep 17 00:00:00 2001 From: reya Date: Sun, 22 Dec 2024 08:25:14 +0700 Subject: [PATCH] wip: migrate to typescript --- biome.json | 2 +- build.js | 104 ++++++------- extension/global.d.ts | 7 + extension/{options.jsx => options.tsx} | 13 +- extension/{popup.jsx => popup.tsx} | 58 ++++--- extension/{prompt.jsx => prompt.tsx} | 35 +++-- package.json | 8 +- pnpm-lock.yaml | 205 +++++++++++++++++-------- tsconfig.json | 24 +++ 9 files changed, 303 insertions(+), 153 deletions(-) create mode 100644 extension/global.d.ts rename extension/{options.jsx => options.tsx} (98%) rename extension/{popup.jsx => popup.tsx} (82%) rename extension/{prompt.jsx => prompt.tsx} (79%) create mode 100644 tsconfig.json diff --git a/biome.json b/biome.json index 2fd46ba..5ba7137 100644 --- a/biome.json +++ b/biome.json @@ -11,7 +11,7 @@ "rules": { "recommended": true, "style": { - "noNonNullAssertion": "warn", + "noNonNullAssertion": "off", "noUselessElse": "off" }, "correctness": { diff --git a/build.js b/build.js index b2aafee..40c4347 100755 --- a/build.js +++ b/build.js @@ -1,56 +1,56 @@ #!/usr/bin/env node -const {copy} = require('esbuild-plugin-copy') -const esbuild = require('esbuild') +import esbuild from "esbuild"; +import copy from "esbuild-copy-plugin"; -const isProd = process.argv.indexOf('prod') !== -1 -const isFirefox = process.argv.indexOf('firefox') !== -1 +const isProd = process.argv.indexOf("prod") !== -1; +const isFirefox = process.argv.indexOf("firefox") !== -1; esbuild - .build({ - bundle: true, - entryPoints: { - 'popup.build': './extension/popup.jsx', - 'prompt.build': './extension/prompt.jsx', - 'options.build': './extension/options.jsx', - 'background.build': './extension/background.js', - 'content-script.build': './extension/content-script.js' - }, - outdir: './extension/output', - sourcemap: isProd ? false : 'inline', - define: { - window: 'self', - global: 'self' - }, - plugins: [ - copy({ - assets: [ - { - from: [ - isFirefox - ? './extension/firefox/manifest.json' - : './extension/chrome/manifest.json' - ], - to: ['./'] - }, - { - from: ['./extension/*.html'], - to: ['./'] - }, - { - from: ['./extension/common.js'], - to: ['./'] - }, - { - from: ['./extension/nostr-provider.js'], - to: ['./'] - }, - { - from: ['./extension/icons/*'], - to: ['./icons'] - } - ] - }) - ] - }) - .then(() => console.log('Build success.')) - .catch(err => console.error('Build error.', err)) + .build({ + bundle: true, + entryPoints: { + "popup.build": "./extension/popup.tsx", + "prompt.build": "./extension/prompt.tsx", + "options.build": "./extension/options.jsx", + "background.build": "./extension/background.js", + "content-script.build": "./extension/content-script.js", + }, + outdir: "./extension/output", + sourcemap: isProd ? false : "inline", + define: { + window: "self", + global: "self", + }, + plugins: [ + copy({ + assets: [ + { + from: [ + isFirefox + ? "./extension/firefox/manifest.json" + : "./extension/chrome/manifest.json", + ], + to: ["./"], + }, + { + from: ["./extension/*.html"], + to: ["./"], + }, + { + from: ["./extension/common.js"], + to: ["./"], + }, + { + from: ["./extension/nostr-provider.js"], + to: ["./"], + }, + { + from: ["./extension/icons/*"], + to: ["./icons"], + }, + ], + }), + ], + }) + .then(() => console.log("Build success.")) + .catch((err) => console.error("Build error.", err)); diff --git a/extension/global.d.ts b/extension/global.d.ts new file mode 100644 index 0000000..c7c4303 --- /dev/null +++ b/extension/global.d.ts @@ -0,0 +1,7 @@ +import type { WindowNostr } from "nostr-tools/nip07"; + +declare global { + interface Window { + nostr?: WindowNostr; + } +} diff --git a/extension/options.jsx b/extension/options.tsx similarity index 98% rename from extension/options.jsx rename to extension/options.tsx index 775e6a5..f827c49 100644 --- a/extension/options.jsx +++ b/extension/options.tsx @@ -1,7 +1,7 @@ import * as Checkbox from "@radix-ui/react-checkbox"; import * as Tabs from "@radix-ui/react-tabs"; -import { generatePrivateKey, nip19 } from "nostr-tools"; -import React, { useState, useCallback, useEffect } from "react"; +import { nip19, generateSecretKey } from "nostr-tools"; +import { useState, useCallback, useEffect } from "react"; import QRCode from "react-qr-code"; import browser from "webextension-polyfill"; import { removePermissions } from "./common"; @@ -17,16 +17,17 @@ function Options() { ); const [hidingPrivateKey, hidePrivateKey] = useState(true); const [showNotifications, setNotifications] = useState(false); - const [messages, setMessages] = useState([]); + const [messages, setMessages] = useState([]); const [handleNostrLinks, setHandleNostrLinks] = useState(false); const [showProtocolHandlerHelp, setShowProtocolHandlerHelp] = useState(false); const [unsavedChanges, setUnsavedChanges] = useState([]); - const showMessage = useCallback((msg) => { + const showMessage = useCallback((msg: string) => { messages.push(msg); + setMessages(messages); setTimeout(() => setMessages([]), 3000); - }); + }, []); useEffect(() => { browser.storage.local @@ -515,7 +516,7 @@ examples: } async function generate() { - setPrivKey(nip19.nsecEncode(generatePrivateKey())); + setPrivKey(nip19.nsecEncode(generateSecretKey())); addUnsavedChanges("private_key"); } diff --git a/extension/popup.jsx b/extension/popup.tsx similarity index 82% rename from extension/popup.jsx rename to extension/popup.tsx index 92316cf..6c19c09 100644 --- a/extension/popup.jsx +++ b/extension/popup.tsx @@ -1,13 +1,21 @@ import * as Tabs from "@radix-ui/react-tabs"; import { minidenticon } from "minidenticons"; import { getPublicKey, nip19 } from "nostr-tools"; -import React, { useState, useMemo, useEffect } from "react"; +import { useState, useMemo, useEffect } from "react"; import QRCode from "react-qr-code"; import browser from "webextension-polyfill"; import { SettingsIcon } from "./icons"; +import { createRoot } from "react-dom/client"; + +type Keys = { + npub: string; + hex: string; + nprofile: string; +}; function Popup() { - const [keys, setKeys] = useState(null); + const [keys, setKeys] = useState(null); + const avatarURI = useMemo( () => keys @@ -25,26 +33,38 @@ function Popup() { useEffect(() => { browser.storage.local.get(["private_key", "relays"]).then((results) => { if (results.private_key) { - const hexKey = getPublicKey(results.private_key); - const npubKey = nip19.npubEncode(hexKey); + const decoded = nip19.decode(results.private_key as unknown as string); - setKeys({ npub: npubKey, hex: hexKey }); + if (decoded.type === "nsec") { + const nsec = decoded.data; + const hexKey = getPublicKey(nsec); + const npubKey = nip19.npubEncode(hexKey); - if (results.relays) { - const relaysList = []; - for (const url in results.relays) { - if (results.relays[url].write) { - relaysList.push(url); - if (relaysList.length >= 3) break; + setKeys({ npub: npubKey, hex: hexKey, nprofile: "" }); + + if (results.relays) { + const relaysList: string[] = []; + + for (const url in results.relays) { + if (results.relays[url].write) { + relaysList.push(url); + if (relaysList.length >= 3) break; + } + } + + if (relaysList.length) { + const nprofileKey = nip19.nprofileEncode({ + pubkey: hexKey, + relays: relaysList, + }); + + setKeys((prev) => + prev ? { ...prev, nprofile: nprofileKey } : null, + ); } } - if (relaysList.length) { - const nprofileKey = nip19.nprofileEncode({ - pubkey: hexKey, - relays: relaysList, - }); - setKeys((prev) => ({ ...prev, nprofile: nprofileKey })); - } + } else { + setKeys(null); } } else { setKeys(null); @@ -179,6 +199,6 @@ function Popup() { } const container = document.getElementById("main"); -const root = createRoot(container); +const root = createRoot(container!); root.render(); diff --git a/extension/prompt.jsx b/extension/prompt.tsx similarity index 79% rename from extension/prompt.jsx rename to extension/prompt.tsx index f6826e7..ecb3ac4 100644 --- a/extension/prompt.jsx +++ b/extension/prompt.tsx @@ -1,4 +1,5 @@ -import React, { useState } from "react"; +import { useState } from "react"; +import { createRoot } from "react-dom/client"; import browser from "webextension-polyfill"; import * as Checkbox from "@radix-ui/react-checkbox"; import { PERMISSION_NAMES } from "./common"; @@ -12,20 +13,27 @@ function Prompt() { const host = qs.get("host"); const type = qs.get("type"); - let params; - let event; + let params: { [key: string]: string } | null; + let event = ""; try { - params = JSON.parse(qs.get("params")); - if (Object.keys(params).length === 0) params = null; - else if (params.event) event = params.event; + params = JSON.parse(qs.get("params") as string); + + if (params) { + if (Object.keys(params).length === 0) { + params = null; + } else if (params.event) { + event = params.event; + } + } } catch (err) { params = null; } - function authorizeHandler(accept) { + function authorizeHandler(accept: boolean) { const conditions = isRemember ? {} : null; - return (ev) => { + + return (ev: React.FormEvent) => { ev.preventDefault(); browser.runtime.sendMessage({ prompt: true, @@ -46,7 +54,8 @@ function Prompt() {

{host}

- is requesting your permission to {PERMISSION_NAMES[type]} + is requesting your permission to{" "} + {PERMISSION_NAMES[type ? type : "unknown"]}

@@ -63,7 +72,7 @@ function Prompt() { setIsRemember((prev) => !prev)} >