mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
refactor(napplets): extract sandbox runtime into :nappletHost module
The `:napplet` sandbox runtime (NappletHostActivity, NappletContentServer, NappletIpc, NappletKeyActions) now lives in a new :nappletHost Android library that depends only on :commons + :quartz — NEVER :amethyst. So the sandbox code is compile-time incapable of importing Amethyst.instance / LocalCache / Account, turning the "two-process, no secrets in the sandbox" rule from a convention into a build-graph guarantee. - New module + NappletHostContract (Intent-extra keys + broker service FQN), so the launcher (amethyst) and activity (module) share the launch contract with no dependency cycle. The activity binds the broker by class name. - Capability labels for the "what it can access" sheet are resolved by the launcher (which has app resources) and passed in, so the module needs no capability string resources. Host-only strings moved into the module. - amethyst depends on :nappletHost; the broker-side (NappletBrokerService, gateways, NappletLaunchRegistry) stays in :amethyst. Manifest declares the activity by FQN (keeps @style/Theme.Amethyst resolvable). - Docs updated (CLAUDE.md + security plan). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
This commit is contained in:
@@ -95,6 +95,10 @@ Application class (there is no per-process Application in the manifest) in **bot
|
||||
(`NappletHostActivity`, declared `android:process=":napplet"`). It holds **no**
|
||||
account or keys; `Amethyst.onCreate()` early-returns here so `Amethyst.instance`
|
||||
is **left unset** (touching it throws `UninitializedPropertyAccessException`).
|
||||
The sandbox runtime lives in its own module **`:nappletHost`** (depends only on
|
||||
`:commons` + `:quartz`, **never** `:amethyst`) so it *cannot* import
|
||||
`Amethyst`/`LocalCache`/`Account` — the broker-side (signer, gateways, registry)
|
||||
stays in `:amethyst` and the two halves talk over Messenger IPC.
|
||||
|
||||
Consequences — don't get caught assuming one process:
|
||||
|
||||
|
||||
@@ -334,6 +334,7 @@ dependencies {
|
||||
implementation(project(":quartz"))
|
||||
implementation(project(":commons"))
|
||||
implementation(project(":nestsClient"))
|
||||
implementation(project(":nappletHost"))
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
|
||||
|
||||
@@ -58,6 +58,13 @@ and what remains as future work.
|
||||
new *signed* manifest and own the app — expected Nostr trust model. Aggregate `x` hash
|
||||
is enforced when present (only *recommended* by the spec); per-path hashes always
|
||||
protect.
|
||||
- **Sandbox isolation is now structural.** The `:napplet` runtime
|
||||
(`NappletHostActivity`, content server, IPC, key actions) lives in its own
|
||||
`:nappletHost` module that depends only on `:commons` + `:quartz` — so it is
|
||||
*compile-time incapable* of importing `Amethyst`/`LocalCache`/`Account`. The
|
||||
broker-side (signer, gateways, `NappletLaunchRegistry`) stays in `:amethyst`;
|
||||
the activity binds the broker service by class-name string and the two halves
|
||||
communicate only over Messenger IPC.
|
||||
- **Launch-token lifecycle.** Tokens are capped (LRU, 128) rather than explicitly
|
||||
unregistered on sandbox close (the sandbox is a separate process and can't reach the
|
||||
main-process registry). A long-backgrounded napplet whose token was evicted would need
|
||||
|
||||
@@ -404,7 +404,7 @@
|
||||
|
||||
<!-- Sandboxed napplet/nsite host. Runs in an isolated process that holds no keys. -->
|
||||
<activity
|
||||
android:name=".napplet.NappletHostActivity"
|
||||
android:name="com.vitorpamplona.amethyst.napplethost.NappletHostActivity"
|
||||
android:process=":napplet"
|
||||
android:exported="false"
|
||||
android:autoRemoveFromRecents="true"
|
||||
|
||||
@@ -40,6 +40,7 @@ import com.vitorpamplona.amethyst.commons.napplet.protocol.NappletProtocolJson
|
||||
import com.vitorpamplona.amethyst.commons.napplet.protocol.NappletResponse
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.napplet.gateways.AccountNappletGateways
|
||||
import com.vitorpamplona.amethyst.napplethost.NappletIpc
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountState
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@@ -23,9 +23,12 @@ package com.vitorpamplona.amethyst.napplet
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletCapability
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletIdentity
|
||||
import com.vitorpamplona.amethyst.commons.napplet.resolveRequiredCapabilities
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.napplethost.NappletHostActivity
|
||||
import com.vitorpamplona.amethyst.napplethost.NappletHostContract
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip5aStaticWebsites.tags.PathTag
|
||||
import com.vitorpamplona.quartz.nip5dNapplets.NappletManifest
|
||||
@@ -38,27 +41,6 @@ import com.vitorpamplona.quartz.nipB7Blossom.BlossomServersEvent
|
||||
* declared capabilities, and a display title. No account state crosses into the sandbox process.
|
||||
*/
|
||||
object NappletLauncher {
|
||||
const val EXTRA_PATHS = "napplet_paths"
|
||||
const val EXTRA_HASHES = "napplet_hashes"
|
||||
const val EXTRA_SERVERS = "napplet_servers"
|
||||
const val EXTRA_AUTHOR = "napplet_author"
|
||||
const val EXTRA_IDENTIFIER = "napplet_identifier"
|
||||
const val EXTRA_AGGREGATE_HASH = "napplet_aggregate_hash"
|
||||
const val EXTRA_TITLE = "napplet_title"
|
||||
|
||||
/** Bare NAP capability domains the manifest declared (empty for a plain nsite). */
|
||||
const val EXTRA_REQUIRES = "napplet_requires"
|
||||
|
||||
/**
|
||||
* Unguessable token for this launch. The sandbox relays it to the broker, which resolves it back
|
||||
* to the trusted identity + declared capabilities via [NappletLaunchRegistry] — the sandbox never
|
||||
* carries (and so can never forge) its own coordinate.
|
||||
*/
|
||||
const val EXTRA_LAUNCH_TOKEN = "napplet_launch_token"
|
||||
|
||||
/** SOCKS proxy port to route blob fetches through, or -1 for a direct connection. */
|
||||
const val EXTRA_PROXY_PORT = "napplet_proxy_port"
|
||||
|
||||
/** Opens a NIP-5D napplet, forwarding its declared capabilities to the broker. */
|
||||
fun launch(
|
||||
context: Context,
|
||||
@@ -107,18 +89,22 @@ object NappletLauncher {
|
||||
val declared = resolveRequiredCapabilities(requires).capabilities.toSet()
|
||||
val launchToken = NappletLaunchRegistry.register(identity, declared)
|
||||
|
||||
// Resolve capability labels here (the app has the resources) so the sandbox module needs none.
|
||||
val capLabels = requires.mapNotNull { NappletCapability.fromNapDomain(it) }.map { context.getString(it.labelRes()) }
|
||||
|
||||
val intent =
|
||||
Intent(context, NappletHostActivity::class.java).apply {
|
||||
putExtra(EXTRA_PATHS, ArrayList(paths.map { it.path }))
|
||||
putExtra(EXTRA_HASHES, ArrayList(paths.map { it.hash }))
|
||||
putExtra(EXTRA_SERVERS, ArrayList(allServers))
|
||||
putExtra(EXTRA_AUTHOR, authorPubKey)
|
||||
putExtra(EXTRA_IDENTIFIER, identifier)
|
||||
putExtra(EXTRA_AGGREGATE_HASH, aggregateHash)
|
||||
putExtra(EXTRA_TITLE, title)
|
||||
putExtra(EXTRA_REQUIRES, ArrayList(requires))
|
||||
putExtra(EXTRA_LAUNCH_TOKEN, launchToken)
|
||||
putExtra(EXTRA_PROXY_PORT, proxyPort)
|
||||
putExtra(NappletHostContract.EXTRA_PATHS, ArrayList(paths.map { it.path }))
|
||||
putExtra(NappletHostContract.EXTRA_HASHES, ArrayList(paths.map { it.hash }))
|
||||
putExtra(NappletHostContract.EXTRA_SERVERS, ArrayList(allServers))
|
||||
putExtra(NappletHostContract.EXTRA_AUTHOR, authorPubKey)
|
||||
putExtra(NappletHostContract.EXTRA_IDENTIFIER, identifier)
|
||||
putExtra(NappletHostContract.EXTRA_AGGREGATE_HASH, aggregateHash)
|
||||
putExtra(NappletHostContract.EXTRA_TITLE, title)
|
||||
putExtra(NappletHostContract.EXTRA_REQUIRES, ArrayList(requires))
|
||||
putExtra(NappletHostContract.EXTRA_CAP_LABELS, ArrayList(capLabels))
|
||||
putExtra(NappletHostContract.EXTRA_LAUNCH_TOKEN, launchToken)
|
||||
putExtra(NappletHostContract.EXTRA_PROXY_PORT, proxyPort)
|
||||
if (context !is android.app.Activity) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
context.startActivity(intent)
|
||||
|
||||
@@ -674,17 +674,8 @@
|
||||
<string name="napplet_permissions_revoke">Revoke</string>
|
||||
<string name="napplet_untitled">Untitled nApplet</string>
|
||||
<string name="napplet_none_found">No nApplets found yet.</string>
|
||||
<string name="napplet_invalid">Invalid nApplet.</string>
|
||||
<string name="napplet_webview_too_old">This device\'s WebView is too old to run nApplets safely.</string>
|
||||
<string name="napplet_fallback_title">nApplet %1$s…</string>
|
||||
<!-- Napplet sandbox chrome (host top bar + live action notices) -->
|
||||
<string name="napplet_chrome_access_title">What “%1$s” can access</string>
|
||||
<string name="napplet_chrome_keys_safe">It can never read your keys, and every sign, publish, upload, or payment was approved by you. Manage access in Settings ▸ nApplets.</string>
|
||||
<string name="napplet_chrome_static_site">Static site — it has no special access to your account.</string>
|
||||
<string name="napplet_chrome_permissions_desc">What this app can access</string>
|
||||
<string name="napplet_action_published">“%1$s” published a note as you</string>
|
||||
<string name="napplet_action_uploaded">“%1$s” uploaded a file</string>
|
||||
<string name="napplet_action_paid">“%1$s” made a payment</string>
|
||||
|
||||
<!-- Napplet capability names -->
|
||||
<string name="napplet_cap_shell">Shell</string>
|
||||
|
||||
@@ -99,6 +99,7 @@ ksp = "2.3.9"
|
||||
abedElazizShe-video-compressor-fork = { group = "com.github.davotoula", name = "LightCompressor-enhanced", version.ref = "lightcompressor-enhanced" }
|
||||
accompanist-adaptive = { group = "com.google.accompanist", name = "accompanist-adaptive", version.ref = "accompanistAdaptive" }
|
||||
accompanist-permissions = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "accompanistAdaptive" }
|
||||
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activityCompose" }
|
||||
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
androidx-appfunctions = { group = "androidx.appfunctions", name = "appfunctions", version.ref = "appfunctions" }
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.androidLibrary)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.vitorpamplona.amethyst.napplethost"
|
||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||
|
||||
defaultConfig {
|
||||
minSdk = libs.versions.android.minSdk.get().toInt()
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_21)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// The sandbox runtime depends ONLY on the protocol/contract (commons) and Nostr resolution
|
||||
// (quartz) — never on :amethyst. This makes it impossible for the `:napplet` process code to
|
||||
// reach for Amethyst.instance / LocalCache / Account (which don't exist in that process).
|
||||
implementation(project(":commons"))
|
||||
implementation(project(":quartz"))
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.activity)
|
||||
implementation(libs.androidx.webkit)
|
||||
implementation(libs.okhttp)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.napplet
|
||||
package com.vitorpamplona.amethyst.napplethost
|
||||
|
||||
import android.util.Log
|
||||
import android.webkit.WebResourceRequest
|
||||
+21
-20
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.napplet
|
||||
package com.vitorpamplona.amethyst.napplethost
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.ComponentName
|
||||
@@ -52,11 +52,10 @@ import androidx.webkit.JavaScriptReplyProxy
|
||||
import androidx.webkit.WebMessageCompat
|
||||
import androidx.webkit.WebViewCompat
|
||||
import androidx.webkit.WebViewFeature
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletCapability
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletWebContract
|
||||
import com.vitorpamplona.amethyst.commons.napplet.protocol.NappletProtocolJson
|
||||
import com.vitorpamplona.amethyst.commons.napplet.resolveRequiredCapabilities
|
||||
import com.vitorpamplona.amethyst.napplethost.R
|
||||
import com.vitorpamplona.quartz.nip5aStaticWebsites.tags.PathTag
|
||||
import org.json.JSONObject
|
||||
|
||||
@@ -88,6 +87,9 @@ class NappletHostActivity : ComponentActivity() {
|
||||
// NAP domain strings the shell advertises to the applet in the shell.init handshake.
|
||||
private var declaredDomains: List<String> = emptyList()
|
||||
|
||||
// Pre-localized capability labels for the "what it can access" sheet (resolved by the launcher).
|
||||
private var capabilityLabels: List<String> = emptyList()
|
||||
|
||||
// Correlation id for id-less fire-and-forget messages so they still reach the broker.
|
||||
private var fireSeq = 0
|
||||
|
||||
@@ -175,7 +177,10 @@ class NappletHostActivity : ComponentActivity() {
|
||||
::onShellMessage,
|
||||
)
|
||||
|
||||
bindService(Intent(this, NappletBrokerService::class.java), brokerConnection, BIND_AUTO_CREATE)
|
||||
// Bind the main-process broker by explicit class name (same APK) rather than a compile-time
|
||||
// class reference, so this sandbox module needs no dependency on :amethyst.
|
||||
val brokerIntent = Intent().setClassName(this, NappletHostContract.BROKER_SERVICE_CLASS)
|
||||
bindService(brokerIntent, brokerConnection, BIND_AUTO_CREATE)
|
||||
|
||||
webView.loadUrl(NappletWebContract.SHELL_URL)
|
||||
}
|
||||
@@ -223,18 +228,19 @@ class NappletHostActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
private fun readManifestExtras(): Boolean {
|
||||
val pathList = intent.getStringArrayListExtra(NappletLauncher.EXTRA_PATHS) ?: return false
|
||||
val hashList = intent.getStringArrayListExtra(NappletLauncher.EXTRA_HASHES) ?: return false
|
||||
val pathList = intent.getStringArrayListExtra(NappletHostContract.EXTRA_PATHS) ?: return false
|
||||
val hashList = intent.getStringArrayListExtra(NappletHostContract.EXTRA_HASHES) ?: return false
|
||||
if (pathList.size != hashList.size || pathList.isEmpty()) return false
|
||||
|
||||
for (i in pathList.indices) paths.add(PathTag(pathList[i], hashList[i]))
|
||||
servers.addAll(intent.getStringArrayListExtra(NappletLauncher.EXTRA_SERVERS) ?: emptyList())
|
||||
author = intent.getStringExtra(NappletLauncher.EXTRA_AUTHOR).orEmpty()
|
||||
title = intent.getStringExtra(NappletLauncher.EXTRA_TITLE).orEmpty()
|
||||
proxyPort = intent.getIntExtra(NappletLauncher.EXTRA_PROXY_PORT, -1)
|
||||
launchToken = intent.getStringExtra(NappletLauncher.EXTRA_LAUNCH_TOKEN).orEmpty()
|
||||
servers.addAll(intent.getStringArrayListExtra(NappletHostContract.EXTRA_SERVERS) ?: emptyList())
|
||||
author = intent.getStringExtra(NappletHostContract.EXTRA_AUTHOR).orEmpty()
|
||||
title = intent.getStringExtra(NappletHostContract.EXTRA_TITLE).orEmpty()
|
||||
proxyPort = intent.getIntExtra(NappletHostContract.EXTRA_PROXY_PORT, -1)
|
||||
launchToken = intent.getStringExtra(NappletHostContract.EXTRA_LAUNCH_TOKEN).orEmpty()
|
||||
capabilityLabels = intent.getStringArrayListExtra(NappletHostContract.EXTRA_CAP_LABELS) ?: emptyList()
|
||||
|
||||
val requires = intent.getStringArrayListExtra(NappletLauncher.EXTRA_REQUIRES) ?: emptyList()
|
||||
val requires = intent.getStringArrayListExtra(NappletHostContract.EXTRA_REQUIRES) ?: emptyList()
|
||||
val resolved = resolveRequiredCapabilities(requires)
|
||||
// shell is always available; the rest are the declared domains advertised to the applet in the
|
||||
// handshake. (The broker enforces the authoritative set from the launch token, not this list.)
|
||||
@@ -437,18 +443,13 @@ class NappletHostActivity : ComponentActivity() {
|
||||
setBackgroundColor(resolveThemeColor(android.R.attr.textColorPrimary) and 0x22FFFFFF)
|
||||
}
|
||||
|
||||
/** Lists, in plain language, exactly which capability domains this napplet was launched with. */
|
||||
/** Lists, in plain language, exactly which capabilities this napplet was launched with. */
|
||||
private fun showAccessDialog() {
|
||||
val caps =
|
||||
declaredDomains
|
||||
.filter { it != "shell" }
|
||||
.mapNotNull { NappletCapability.fromNapDomain(it) }
|
||||
.map { getString(it.labelRes()) }
|
||||
val body =
|
||||
if (caps.isEmpty()) {
|
||||
if (capabilityLabels.isEmpty()) {
|
||||
getString(R.string.napplet_chrome_static_site)
|
||||
} else {
|
||||
caps.joinToString("\n") { "• $it" } + "\n\n" + getString(R.string.napplet_chrome_keys_safe)
|
||||
capabilityLabels.joinToString("\n") { "• $it" } + "\n\n" + getString(R.string.napplet_chrome_keys_safe)
|
||||
}
|
||||
AlertDialog
|
||||
.Builder(this)
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.napplethost
|
||||
|
||||
/**
|
||||
* Intent-extra contract for launching [NappletHostActivity]. Lives in the sandbox module so the
|
||||
* activity (here) and the launcher (in `:amethyst`) share the keys without a dependency cycle —
|
||||
* `:amethyst` depends on `:nappletHost`, never the other way around.
|
||||
*/
|
||||
object NappletHostContract {
|
||||
const val EXTRA_PATHS = "napplet_paths"
|
||||
const val EXTRA_HASHES = "napplet_hashes"
|
||||
const val EXTRA_SERVERS = "napplet_servers"
|
||||
const val EXTRA_AUTHOR = "napplet_author"
|
||||
const val EXTRA_IDENTIFIER = "napplet_identifier"
|
||||
const val EXTRA_AGGREGATE_HASH = "napplet_aggregate_hash"
|
||||
const val EXTRA_TITLE = "napplet_title"
|
||||
|
||||
/** Bare NAP capability domains the manifest declared (empty for a plain nSite). */
|
||||
const val EXTRA_REQUIRES = "napplet_requires"
|
||||
|
||||
/**
|
||||
* Pre-localized capability labels for the "what it can access" sheet, resolved by the launcher
|
||||
* (which has the app's resources) so the sandbox module needs no capability string resources.
|
||||
*/
|
||||
const val EXTRA_CAP_LABELS = "napplet_cap_labels"
|
||||
|
||||
/**
|
||||
* Unguessable token for this launch. The sandbox relays it to the broker, which resolves it back
|
||||
* to the trusted identity + declared capabilities — the sandbox never carries (and so can never
|
||||
* forge) its own coordinate.
|
||||
*/
|
||||
const val EXTRA_LAUNCH_TOKEN = "napplet_launch_token"
|
||||
|
||||
/** SOCKS proxy port to route blob fetches through, or -1 for a direct connection. */
|
||||
const val EXTRA_PROXY_PORT = "napplet_proxy_port"
|
||||
|
||||
/**
|
||||
* FQN of the main-process broker service (in `:amethyst`). The sandbox binds it by name so it
|
||||
* needs no compile-time reference to `:amethyst`. Must match the manifest `<service>` declaration.
|
||||
*/
|
||||
const val BROKER_SERVICE_CLASS = "com.vitorpamplona.amethyst.napplet.NappletBrokerService"
|
||||
}
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.napplet
|
||||
package com.vitorpamplona.amethyst.napplethost
|
||||
|
||||
/**
|
||||
* The Messenger wire contract between the untrusted `:napplet` process (the WebView host) and
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.napplet
|
||||
package com.vitorpamplona.amethyst.napplethost
|
||||
|
||||
import android.view.KeyEvent
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Sandbox host (`:napplet` process) UI. Kept here so the sandbox module needs no app resources. -->
|
||||
<string name="napplet_invalid">Invalid nApplet.</string>
|
||||
<string name="napplet_webview_too_old">This device\'s WebView is too old to run nApplets safely.</string>
|
||||
<string name="napplet_untitled">Untitled nApplet</string>
|
||||
|
||||
<!-- Trusted chrome (top bar + live action notices) -->
|
||||
<string name="napplet_chrome_access_title">What “%1$s” can access</string>
|
||||
<string name="napplet_chrome_keys_safe">It can never read your keys, and every sign, publish, upload, or payment was approved by you. Manage access in Settings ▸ nApplets.</string>
|
||||
<string name="napplet_chrome_static_site">Static site — it has no special access to your account.</string>
|
||||
<string name="napplet_chrome_permissions_desc">What this app can access</string>
|
||||
<string name="napplet_action_published">“%1$s” published a note as you</string>
|
||||
<string name="napplet_action_uploaded">“%1$s” uploaded a file</string>
|
||||
<string name="napplet_action_paid">“%1$s” made a payment</string>
|
||||
</resources>
|
||||
@@ -31,6 +31,7 @@ dependencyResolutionManagement {
|
||||
|
||||
rootProject.name = "Amethyst"
|
||||
include(":amethyst")
|
||||
include(":nappletHost")
|
||||
include(":benchmark")
|
||||
include(":quartz")
|
||||
include(":geode")
|
||||
|
||||
Reference in New Issue
Block a user