pakstr 0.21.1 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/android-template/app/src/androidTest/java/com/pakstr/app/Nip55CallbackTest.kt +1 -1
- package/android-template/app/src/main/java/com/pakstr/app/LocalServer.kt +12 -28
- package/android-template/app/src/main/java/com/pakstr/app/Nip55Callback.kt +3 -0
- package/android-template/app/src/main/java/com/pakstr/app/Nip55SignEventRequest.kt +303 -0
- package/android-template/app/src/main/java/com/pakstr/app/PakstrBridge.kt +14 -2
- package/android-template/app/src/main/java/com/pakstr/app/RuntimeApiBaseSettings.kt +98 -0
- package/android-template/app/src/main/java/com/pakstr/app/RuntimeApiBaseValidator.kt +30 -0
- package/android-template/app/src/main/java/com/pakstr/app/RuntimeConfig.kt +7 -12
- package/android-template/app/src/main/java/com/pakstr/app/WebViewController.kt +67 -17
- package/android-template/app/src/main/java/com/pakstr/app/debug/DeveloperToolsActivity.kt +70 -4
- package/android-template/app/src/main/res/layout/activity_developer_tools.xml +257 -66
- package/android-template/app/src/main/res/values/strings.xml +24 -5
- package/android-template/app/src/test/java/com/pakstr/app/Nip55SignEventDispatchTest.kt +115 -0
- package/android-template/app/src/test/java/com/pakstr/app/Nip55SignEventRequestTest.kt +90 -0
- package/android-template/app/src/test/java/com/pakstr/app/PakstrBridgeRuntimeApiBaseTest.kt +52 -0
- package/android-template/app/src/test/java/com/pakstr/app/RuntimeApiBaseProxyTest.kt +49 -0
- package/android-template/app/src/test/java/com/pakstr/app/RuntimeApiBaseSettingsTest.kt +107 -0
- package/android-template/app/src/test/java/com/pakstr/app/RuntimeApiBaseValidatorTest.kt +50 -0
- package/package.json +1 -1
|
@@ -17,6 +17,13 @@ import com.pakstr.app.debug.AppDebugLogger
|
|
|
17
17
|
import com.pakstr.app.permissions.interfaces.PermissionHandler
|
|
18
18
|
import com.pakstr.app.signer.NostrBridge
|
|
19
19
|
import com.pakstr.app.signer.SignerManager
|
|
20
|
+
import kotlinx.coroutines.CancellationException
|
|
21
|
+
import kotlinx.coroutines.CoroutineScope
|
|
22
|
+
import kotlinx.coroutines.Dispatchers
|
|
23
|
+
import kotlinx.coroutines.SupervisorJob
|
|
24
|
+
import kotlinx.coroutines.cancel
|
|
25
|
+
import kotlinx.coroutines.launch
|
|
26
|
+
import kotlinx.coroutines.withContext
|
|
20
27
|
|
|
21
28
|
/**
|
|
22
29
|
* Controls WebView configuration and lifecycle.
|
|
@@ -44,6 +51,7 @@ class WebViewController(
|
|
|
44
51
|
) {
|
|
45
52
|
private var pendingNip55Result: String? = null
|
|
46
53
|
private var isSetup = false
|
|
54
|
+
private val nip55Scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
|
47
55
|
|
|
48
56
|
// Keeps a reference to the runnable to prevent memory leaks if destroyed early
|
|
49
57
|
private val loadRunnable = Runnable {
|
|
@@ -228,22 +236,37 @@ class WebViewController(
|
|
|
228
236
|
): Boolean {
|
|
229
237
|
val url = request?.url ?: return true
|
|
230
238
|
if (url.scheme.equals("nostrsigner", ignoreCase = true)) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
239
|
+
dispatchNip55SignEvent(
|
|
240
|
+
rawUrl = url.toString(),
|
|
241
|
+
activeSignerType = signerManager.getActiveType(),
|
|
242
|
+
expectedCallbackUrl = Nip55Callback.callbackPrefix,
|
|
243
|
+
forwardExternally = { launchExternalNip55(url) },
|
|
244
|
+
launchSigning = { operation ->
|
|
245
|
+
nip55Scope.launch {
|
|
246
|
+
try {
|
|
247
|
+
operation()
|
|
248
|
+
} catch (error: CancellationException) {
|
|
249
|
+
throw error
|
|
250
|
+
} catch (error: Exception) {
|
|
251
|
+
AppDebugLogger.log(
|
|
252
|
+
context,
|
|
253
|
+
"NIP55",
|
|
254
|
+
"Bunker flow failed: ${error::class.java.simpleName}"
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
getActivePubkey = signerManager::getCurrentUserPubkey,
|
|
260
|
+
signEvent = { event ->
|
|
261
|
+
signerManager.getSigner().signEvent(event)
|
|
262
|
+
},
|
|
263
|
+
deliverSignedEvent = { signedEvent ->
|
|
264
|
+
withContext(Dispatchers.Main) {
|
|
265
|
+
handleNip55Callback(signedEvent)
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
logRejection = ::logNip55Rejection
|
|
269
|
+
)
|
|
247
270
|
return true
|
|
248
271
|
}
|
|
249
272
|
return url.host != "127.0.0.1"
|
|
@@ -316,7 +339,7 @@ class WebViewController(
|
|
|
316
339
|
|
|
317
340
|
)
|
|
318
341
|
webView.addJavascriptInterface(
|
|
319
|
-
PakstrBridge(),
|
|
342
|
+
PakstrBridge(RuntimeApiBaseSettings.from(context)),
|
|
320
343
|
"PakstrBridge"
|
|
321
344
|
)
|
|
322
345
|
|
|
@@ -355,7 +378,34 @@ class WebViewController(
|
|
|
355
378
|
(context as? Activity)?.findViewById<View>(R.id.loader)?.visibility = View.GONE
|
|
356
379
|
}
|
|
357
380
|
|
|
381
|
+
private fun launchExternalNip55(url: android.net.Uri) {
|
|
382
|
+
try {
|
|
383
|
+
val intent = Intent(Intent.ACTION_VIEW, url).apply {
|
|
384
|
+
putExtra(
|
|
385
|
+
Browser.EXTRA_APPLICATION_ID,
|
|
386
|
+
context.packageName
|
|
387
|
+
)
|
|
388
|
+
}
|
|
389
|
+
context.startActivity(intent)
|
|
390
|
+
} catch (_: ActivityNotFoundException) {
|
|
391
|
+
AppDebugLogger.error(
|
|
392
|
+
context,
|
|
393
|
+
"WEBVIEW",
|
|
394
|
+
"No app can handle nostrsigner URL"
|
|
395
|
+
)
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
private fun logNip55Rejection(reason: String) {
|
|
400
|
+
AppDebugLogger.log(
|
|
401
|
+
context,
|
|
402
|
+
"NIP55",
|
|
403
|
+
"Request rejected: $reason"
|
|
404
|
+
)
|
|
405
|
+
}
|
|
406
|
+
|
|
358
407
|
fun destroy() {
|
|
408
|
+
nip55Scope.cancel()
|
|
359
409
|
webView.stopLoading()
|
|
360
410
|
|
|
361
411
|
webView.clearHistory()
|
|
@@ -15,13 +15,18 @@ import android.widget.TextView
|
|
|
15
15
|
import android.widget.Toast
|
|
16
16
|
import androidx.activity.enableEdgeToEdge
|
|
17
17
|
import androidx.appcompat.app.AppCompatActivity
|
|
18
|
+
import androidx.core.view.ViewCompat
|
|
19
|
+
import androidx.core.view.WindowInsetsCompat
|
|
20
|
+
import androidx.core.view.updatePadding
|
|
18
21
|
import com.google.android.material.appbar.MaterialToolbar
|
|
19
22
|
import com.google.android.material.button.MaterialButton
|
|
20
23
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
21
24
|
import com.google.android.material.switchmaterial.SwitchMaterial
|
|
25
|
+
import com.google.android.material.textfield.TextInputEditText
|
|
26
|
+
import com.google.android.material.textfield.TextInputLayout
|
|
22
27
|
import com.pakstr.app.BuildConfig
|
|
23
28
|
import com.pakstr.app.R
|
|
24
|
-
import com.pakstr.app.
|
|
29
|
+
import com.pakstr.app.RuntimeApiBaseSettings
|
|
25
30
|
|
|
26
31
|
class DeveloperToolsActivity : AppCompatActivity() {
|
|
27
32
|
|
|
@@ -30,8 +35,12 @@ class DeveloperToolsActivity : AppCompatActivity() {
|
|
|
30
35
|
private lateinit var uptimeValue: TextView
|
|
31
36
|
private lateinit var debugModeSwitch: SwitchMaterial
|
|
32
37
|
private lateinit var apiSwitch: SwitchMaterial
|
|
38
|
+
private lateinit var apiBaseInputLayout: TextInputLayout
|
|
39
|
+
private lateinit var apiBaseInput: TextInputEditText
|
|
40
|
+
private lateinit var apiBaseSource: TextView
|
|
33
41
|
private lateinit var debugWebViewStatus: TextView
|
|
34
42
|
private lateinit var webViewStatus: TextView
|
|
43
|
+
private lateinit var runtimeApiBaseSettings: RuntimeApiBaseSettings
|
|
35
44
|
|
|
36
45
|
private val uptimeUpdater = object : Runnable {
|
|
37
46
|
override fun run() {
|
|
@@ -50,11 +59,14 @@ class DeveloperToolsActivity : AppCompatActivity() {
|
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
setContentView(R.layout.activity_developer_tools)
|
|
62
|
+
runtimeApiBaseSettings = RuntimeApiBaseSettings.from(this)
|
|
53
63
|
|
|
64
|
+
setupWindowInsets()
|
|
54
65
|
setupToolbar()
|
|
55
66
|
bindViews()
|
|
56
67
|
populateRuntimeDetails()
|
|
57
68
|
setupDebugControls()
|
|
69
|
+
setupApiBaseControls()
|
|
58
70
|
setupLogActions()
|
|
59
71
|
setupCrashTesting()
|
|
60
72
|
setupWebViewActions()
|
|
@@ -71,6 +83,18 @@ class DeveloperToolsActivity : AppCompatActivity() {
|
|
|
71
83
|
super.onStop()
|
|
72
84
|
}
|
|
73
85
|
|
|
86
|
+
private fun setupWindowInsets() {
|
|
87
|
+
ViewCompat.setOnApplyWindowInsetsListener(
|
|
88
|
+
findViewById(R.id.developerToolsRoot)
|
|
89
|
+
) { view, insets ->
|
|
90
|
+
val bars = insets.getInsets(
|
|
91
|
+
WindowInsetsCompat.Type.systemBars()
|
|
92
|
+
)
|
|
93
|
+
view.updatePadding(top = bars.top)
|
|
94
|
+
insets
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
74
98
|
private fun setupToolbar() {
|
|
75
99
|
findViewById<MaterialToolbar>(R.id.developerToolsToolbar).setNavigationOnClickListener {
|
|
76
100
|
finish()
|
|
@@ -81,6 +105,9 @@ class DeveloperToolsActivity : AppCompatActivity() {
|
|
|
81
105
|
uptimeValue = findViewById(R.id.runtimeUptimeValue)
|
|
82
106
|
debugModeSwitch = findViewById(R.id.debugModeSwitch)
|
|
83
107
|
apiSwitch = findViewById(R.id.apiEnabledSwitch)
|
|
108
|
+
apiBaseInputLayout = findViewById(R.id.apiBaseInputLayout)
|
|
109
|
+
apiBaseInput = findViewById(R.id.apiBaseInput)
|
|
110
|
+
apiBaseSource = findViewById(R.id.apiBaseSource)
|
|
84
111
|
debugWebViewStatus = findViewById(R.id.debugWebViewStatus)
|
|
85
112
|
webViewStatus = findViewById(R.id.webViewStatus)
|
|
86
113
|
}
|
|
@@ -101,8 +128,6 @@ class DeveloperToolsActivity : AppCompatActivity() {
|
|
|
101
128
|
}
|
|
102
129
|
|
|
103
130
|
private fun setupDebugControls() {
|
|
104
|
-
apiSwitch.isChecked = RuntimeConfig.apiEnabled(this)
|
|
105
|
-
|
|
106
131
|
debugModeSwitch.setOnCheckedChangeListener { _, isChecked ->
|
|
107
132
|
if (isChecked) {
|
|
108
133
|
DeveloperToolsManager.enableDebug(this)
|
|
@@ -115,9 +140,50 @@ class DeveloperToolsActivity : AppCompatActivity() {
|
|
|
115
140
|
}
|
|
116
141
|
}
|
|
117
142
|
|
|
143
|
+
private fun setupApiBaseControls() {
|
|
144
|
+
findViewById<MaterialButton>(R.id.saveApiBaseButton).setOnClickListener {
|
|
145
|
+
val value = apiBaseInput.text?.toString().orEmpty()
|
|
146
|
+
if (runtimeApiBaseSettings.setOverride(value)) {
|
|
147
|
+
apiBaseInputLayout.error = null
|
|
148
|
+
refreshApiBase()
|
|
149
|
+
showMessage(R.string.developer_tools_api_base_saved)
|
|
150
|
+
} else {
|
|
151
|
+
apiBaseInputLayout.error = getString(
|
|
152
|
+
R.string.developer_tools_api_base_invalid
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
findViewById<MaterialButton>(R.id.resetApiBaseButton).setOnClickListener {
|
|
157
|
+
if (runtimeApiBaseSettings.reset()) {
|
|
158
|
+
apiBaseInputLayout.error = null
|
|
159
|
+
refreshApiBase()
|
|
160
|
+
showMessage(R.string.developer_tools_api_base_reset)
|
|
161
|
+
} else {
|
|
162
|
+
showMessage(R.string.developer_tools_api_base_save_failed)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
refreshApiBase()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private fun refreshApiBase() {
|
|
169
|
+
val current = runtimeApiBaseSettings.current()
|
|
170
|
+
apiSwitch.isChecked = current.url != null
|
|
171
|
+
apiBaseInput.setText(current.url.orEmpty())
|
|
172
|
+
apiBaseSource.setText(
|
|
173
|
+
when (current.source) {
|
|
174
|
+
RuntimeApiBaseSettings.Source.RUNTIME_OVERRIDE ->
|
|
175
|
+
R.string.developer_tools_api_base_runtime_override
|
|
176
|
+
RuntimeApiBaseSettings.Source.PACKAGED_DEFAULT ->
|
|
177
|
+
R.string.developer_tools_api_base_packaged_default
|
|
178
|
+
RuntimeApiBaseSettings.Source.DISABLED ->
|
|
179
|
+
R.string.developer_tools_api_base_not_configured
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
118
184
|
private fun refreshStatuses() {
|
|
119
185
|
debugModeSwitch.isChecked = DeveloperToolsManager.isEnabled(this)
|
|
120
|
-
|
|
186
|
+
refreshApiBase()
|
|
121
187
|
|
|
122
188
|
val status = if (isWebViewDebugEnabled()) {
|
|
123
189
|
R.string.developer_tools_enabled
|