From 0adee41711090d497a5376f0d148f828808be828 Mon Sep 17 00:00:00 2001 From: davotoula Date: Wed, 1 Jul 2026 09:37:57 +0200 Subject: [PATCH] refactor: extract duplicated string literals into named constants --- .../java/com/vitorpamplona/amethyst/ui/MainActivity.kt | 10 ++++++---- .../amethyst/cli/commands/NostrConnect.kt | 8 +++++--- .../amethyst/napplethost/NappletBrowserService.kt | 5 +++-- .../amethyst/napplethost/NappletContentServer.kt | 7 ++++--- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt index eeef549f91..66d837e82d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt @@ -145,6 +145,8 @@ class MainActivity : AppCompatActivity() { } } +private const val NOSTR_URI_PREFIX = "nostr:" + fun isNotificationRoute(uri: String) = uri.startsWith("notifications", true) || uri.startsWith("nostr:notifications", true) fun isHashtagRoute(uri: String) = uri.startsWith("hashtag?id=") || uri.startsWith("nostr:hashtag?id=") @@ -161,7 +163,7 @@ fun isConnectedAppRoute(uri: String) = uri.startsWith("connectedapp?coordinate=" fun connectedAppRoute(uri: String): Route.ConnectedAppDetail? { val coordinate = runCatching { - val raw = java.net.URI(uri.removePrefix("nostr:")).findParameterValue("coordinate") ?: return null + val raw = java.net.URI(uri.removePrefix(NOSTR_URI_PREFIX)).findParameterValue("coordinate") ?: return null URLDecoder.decode(raw, Charsets.UTF_8.name()) }.getOrNull()?.takeIf { it.isNotBlank() } ?: return null @@ -171,7 +173,7 @@ fun connectedAppRoute(uri: String): Route.ConnectedAppDetail? { fun urlRoute(uri: String): Route.Url? { val url = runCatching { - val rawUrl = java.net.URI(uri.removePrefix("nostr:")).findParameterValue("id") ?: return null + val rawUrl = java.net.URI(uri.removePrefix(NOSTR_URI_PREFIX)).findParameterValue("id") ?: return null URLDecoder.decode(rawUrl, Charsets.UTF_8.name()) }.getOrNull() ?: return null @@ -189,11 +191,11 @@ fun uriToRoute( account: Account, ): Route? { if (isNotificationRoute(uri)) { - val scrollTo = runCatching { java.net.URI(uri.removePrefix("nostr:")).findParameterValue("scrollTo") }.getOrNull() + val scrollTo = runCatching { java.net.URI(uri.removePrefix(NOSTR_URI_PREFIX)).findParameterValue("scrollTo") }.getOrNull() return Route.Notification(scrollToEventId = scrollTo) } if (isHashtagRoute(uri)) { - return Route.Hashtag(uri.removePrefix("nostr:").removePrefix("hashtag?id=").lowercase()) + return Route.Hashtag(uri.removePrefix(NOSTR_URI_PREFIX).removePrefix("hashtag?id=").lowercase()) } if (isUrlRoute(uri)) { return urlRoute(uri) diff --git a/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/NostrConnect.kt b/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/NostrConnect.kt index 29810dffaa..c26c166854 100644 --- a/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/NostrConnect.kt +++ b/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/NostrConnect.kt @@ -51,6 +51,8 @@ import okhttp3.OkHttpClient * which only parses). The signer side lives in [BunkerCommand]. */ object NostrConnect { + private const val NOSTRCONNECT_SCHEME = "nostrconnect://" + data class Offer( val clientPubkey: String, val relays: Set, @@ -60,8 +62,8 @@ object NostrConnect { /** Parse `nostrconnect://?relay=…&secret=…&name=…` (percent-decoded). */ fun parseOffer(uri: String): Offer? { - if (!uri.startsWith("nostrconnect://")) return null - val parts = uri.removePrefix("nostrconnect://").split("?", limit = 2) + if (!uri.startsWith(NOSTRCONNECT_SCHEME)) return null + val parts = uri.removePrefix(NOSTRCONNECT_SCHEME).split("?", limit = 2) val clientPubkey = parts[0].lowercase() if (clientPubkey.length != 64 || clientPubkey.any { it !in "0123456789abcdef" }) return null val relays = mutableSetOf() @@ -89,7 +91,7 @@ object NostrConnect { ): String { val enc = { s: String -> java.net.URLEncoder.encode(s, "UTF-8") } return buildString { - append("nostrconnect://").append(clientPubkey) + append(NOSTRCONNECT_SCHEME).append(clientPubkey) append("?").append(relays.joinToString("&") { "relay=${enc(it.url)}" }) append("&secret=").append(enc(secret)) if (name != null) append("&name=").append(enc(name)) diff --git a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletBrowserService.kt b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletBrowserService.kt index 3ba6e87554..43556385a0 100644 --- a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletBrowserService.kt +++ b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletBrowserService.kt @@ -153,7 +153,7 @@ class NappletBrowserService : Service() { BrowserTab( sessionId = sessionId, clientMessenger = msg.replyTo, - url = data.getString(NappletBrowserContract.KEY_URL)?.ifBlank { "about:blank" } ?: "about:blank", + url = data.getString(NappletBrowserContract.KEY_URL)?.ifBlank { ABOUT_BLANK } ?: ABOUT_BLANK, proxyPort = data.getInt(NappletBrowserContract.KEY_PROXY_PORT, -1), useTor = data.getBoolean(NappletBrowserContract.KEY_USE_TOR, false), bgColor = data.getInt(NappletBrowserContract.KEY_BG_COLOR, android.graphics.Color.WHITE), @@ -590,9 +590,10 @@ class NappletBrowserService : Service() { private fun readContractAsset(path: String): ByteArray = assets.open(NappletWebContract.RESOURCE_ASSET_ROOT + path).use { it.readBytes() } /** Address-bar text → URL via the shared [OmniboxInput] rules (bare domain → https, else search). */ - private fun normalizeUrl(input: String): String = OmniboxInput.resolve(input)?.url ?: "about:blank" + private fun normalizeUrl(input: String): String = OmniboxInput.resolve(input)?.url ?: ABOUT_BLANK private companion object { private const val TAG = "NappletBrowserService" + private const val ABOUT_BLANK = "about:blank" } } diff --git a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletContentServer.kt b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletContentServer.kt index e4fce4c3d6..7f32a39474 100644 --- a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletContentServer.kt +++ b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletContentServer.kt @@ -121,7 +121,7 @@ class NappletContentServer( if (url == NappletWebContract.SHELL_URL) return serveShell() if (url == appOrigin || url.startsWith("$appOrigin/")) { // A document navigation accepts text/html; a sub-resource (js/css/img) does not. - val acceptsHtml = request.requestHeaders["Accept"]?.contains("text/html", ignoreCase = true) == true + val acceptsHtml = request.requestHeaders["Accept"]?.contains(MIME_HTML, ignoreCase = true) == true return serveAppResource(url, acceptsHtml) } // Off-origin: a locked napplet 404s (connect-src 'none' means it shouldn't ask). An nSite in @@ -134,7 +134,7 @@ class NappletContentServer( // origin so the shell frames exactly this applet (and the CSP frame-src is pinned to it too). val html = shellHtmlBytes.decodeToString().replace(NappletWebContract.APP_ORIGIN_PLACEHOLDER, appOrigin).encodeToByteArray() return WebResourceResponse( - "text/html", + MIME_HTML, "utf-8", 200, "OK", @@ -167,7 +167,7 @@ class NappletContentServer( if (resolution !is StaticSiteResolution.Resolved) return notFound() val (mime, charset) = splitContentType(resolution.contentType) - val isHtml = mime.equals("text/html", ignoreCase = true) + val isHtml = mime.equals(MIME_HTML, ignoreCase = true) val bytes = if (isHtml) injectShim(resolution.bytes) else resolution.bytes // Locked napplets get the strict app CSP (connect-src 'none', etc.). An nSite in website mode @@ -222,5 +222,6 @@ class NappletContentServer( companion object { // Marker "server" for a Resolved served from the local content-addressed cache. private const val CACHE_SERVER = "cache" + private const val MIME_HTML = "text/html" } }