pakstr 0.0.4 → 0.2.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.
Files changed (32) hide show
  1. package/android-template/app/build.gradle.kts +3 -0
  2. package/android-template/app/src/androidTest/java/com/pakstr/app/ExampleInstrumentedTest.kt +8 -3
  3. package/android-template/app/src/androidTest/java/com/pakstr/app/signer/NostrEventBuilderTest.kt +67 -0
  4. package/android-template/app/src/androidTest/java/com/pakstr/app/signer/NostrSignatureVerifierTest.kt +43 -0
  5. package/android-template/app/src/main/AndroidManifest.xml +15 -3
  6. package/android-template/app/src/main/assets/nip07.js +85 -4
  7. package/android-template/app/src/main/assets/www/index.html +215 -0
  8. package/android-template/app/src/main/java/com/pakstr/app/ApplicationClass.kt +2 -0
  9. package/android-template/app/src/main/java/com/pakstr/app/LocalServer.kt +35 -10
  10. package/android-template/app/src/main/java/com/pakstr/app/MainActivity.kt +121 -101
  11. package/android-template/app/src/main/java/com/pakstr/app/ShellRuntime.kt +5 -6
  12. package/android-template/app/src/main/java/com/pakstr/app/WebViewController.kt +69 -20
  13. package/android-template/app/src/main/java/com/pakstr/app/crypto/Bip340Verifier.kt +318 -0
  14. package/android-template/app/src/main/java/com/pakstr/app/crypto/CryptoInitializer.kt +34 -0
  15. package/android-template/app/src/main/java/com/pakstr/app/crypto/Nip04Crypto.kt +33 -0
  16. package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEncryption.kt +16 -0
  17. package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEventHasher.kt +100 -0
  18. package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEventVerifier.kt +54 -0
  19. package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrSignatureVerifier.kt +43 -0
  20. package/android-template/app/src/main/java/com/pakstr/app/crypto/Secp256k1.kt +63 -0
  21. package/android-template/app/src/main/java/com/pakstr/app/debug/AppDebugLogger.kt +21 -0
  22. package/android-template/app/src/main/java/com/pakstr/app/signer/AmberSigner.kt +427 -74
  23. package/android-template/app/src/main/java/com/pakstr/app/signer/BunkerSigner.kt +14 -0
  24. package/android-template/app/src/main/java/com/pakstr/app/signer/FakeSigner.kt +14 -0
  25. package/android-template/app/src/main/java/com/pakstr/app/signer/NostrBridge.kt +297 -32
  26. package/android-template/app/src/main/java/com/pakstr/app/signer/NostrEventBuilder.kt +39 -0
  27. package/android-template/app/src/main/java/com/pakstr/app/signer/SignerManager.kt +103 -73
  28. package/android-template/app/src/main/java/com/pakstr/app/signer/SignerType.kt +0 -1
  29. package/android-template/app/src/main/java/com/pakstr/app/signer/exceptions/SignerUnavailableException.kt +5 -0
  30. package/android-template/app/src/main/java/com/pakstr/app/signer/interfaces/NostrSigner.kt +10 -0
  31. package/android-template/app/src/main/res/values/strings.xml +1 -1
  32. package/package.json +1 -1
@@ -1,153 +1,153 @@
1
1
  package com.pakstr.app
2
2
 
3
3
  import android.Manifest
4
+ import android.content.Intent
4
5
  import android.os.Bundle
5
6
  import android.util.Log
7
+
6
8
  import android.view.View
7
9
  import android.webkit.PermissionRequest
8
10
  import android.webkit.WebView
9
11
  import android.widget.FrameLayout
10
12
  import androidx.activity.OnBackPressedCallback
11
13
  import androidx.activity.enableEdgeToEdge
14
+ import androidx.activity.result.ActivityResultLauncher
12
15
  import androidx.activity.result.contract.ActivityResultContracts
13
16
  import androidx.appcompat.app.AppCompatActivity
14
17
  import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
15
18
  import androidx.core.view.ViewCompat
16
19
  import androidx.core.view.WindowInsetsCompat
17
20
  import androidx.core.view.updatePadding
21
+ import androidx.lifecycle.lifecycleScope
18
22
  import com.pakstr.app.permissions.PermissionManager
19
23
  import com.pakstr.app.permissions.interfaces.PermissionHandler
24
+ import com.pakstr.app.crypto.CryptoInitializer
25
+ import com.pakstr.app.debug.AppDebugLogger
26
+
20
27
  import com.pakstr.app.signer.SignerManager
21
- import com.pakstr.app.signer.interfaces.NostrSigner
28
+ import com.pakstr.app.signer.SignerType
22
29
  import com.pakstr.app.utils.AppPreferences
23
30
  import com.pakstr.app.utils.SignerPickerBottomSheet
31
+ import kotlinx.coroutines.launch
24
32
 
25
33
  class MainActivity : AppCompatActivity(), PermissionHandler {
26
34
 
27
35
  private lateinit var runtime: ShellRuntime
28
36
  private lateinit var container: FrameLayout
29
37
  private lateinit var webView: WebView
30
-
31
38
  private lateinit var loader: View
32
-
33
39
  private lateinit var signerManager: SignerManager
34
-
35
- private var activeSigner: NostrSigner? = null
36
-
37
- private var pendingPermissionRequest: PermissionRequest? = null
38
-
39
40
  private lateinit var permissionManager: PermissionManager
41
+ private lateinit var amberLauncher:
42
+ ActivityResultLauncher<Intent>
40
43
 
41
- private val amberLauncher =
42
-
43
- registerForActivityResult(
44
-
45
- ActivityResultContracts.StartActivityForResult()
46
-
47
- ) { result ->
48
-
49
- signerManager.handleAmberResult(result)
50
-
51
- }
44
+ override fun onCreate(
45
+ savedInstanceState: Bundle?
46
+ ) {
52
47
 
53
- override fun onCreate(savedInstanceState: Bundle?) {
54
- val splashScreen = installSplashScreen()
48
+ installSplashScreen()
55
49
  enableEdgeToEdge()
56
50
 
57
51
  super.onCreate(savedInstanceState)
58
- setContentView(R.layout.activity_layout)
59
52
 
60
- permissionManager = PermissionManager(this)
53
+ if (BuildConfig.DEBUG) {
61
54
 
62
- signerManager = SignerManager(
55
+ WebView.setWebContentsDebuggingEnabled(true)
63
56
 
64
- this
65
-
66
- ) { intent ->
57
+ }
67
58
 
68
- amberLauncher.launch(intent)
59
+ setContentView(
60
+ R.layout.activity_layout
61
+ )
69
62
 
70
- }
63
+ initViews()
71
64
 
72
- val isFirstRun = AppPreferences.getBoolean("isFirstRun", true)
65
+ amberLauncher =
66
+ registerForActivityResult(
67
+ ActivityResultContracts.StartActivityForResult()
68
+ ) { result ->
69
+ signerManager.handleAmberResult(result)
70
+ }
73
71
 
74
- if (isFirstRun) {
75
- //showTwoOptionsPicker(prefs)
76
- showSignerPicker()
77
- //testPush()
78
- }
72
+ initSigner()
73
+ signerManager.setActiveType(
74
+ SignerType.AMBER
75
+ )
76
+ //if (AppPreferences.getBoolean("isFirstRun", true)) {
77
+ //showSignerPicker()
78
+ //}
79
79
 
80
- initViews()
80
+ ViewCompat.setOnApplyWindowInsetsListener(
81
+ container
82
+ ) { view, insets ->
81
83
 
82
- ViewCompat.setOnApplyWindowInsetsListener(container) { view, insets ->
83
- val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
84
+ val bars =
85
+ insets.getInsets(
86
+ WindowInsetsCompat.Type.systemBars()
87
+ )
84
88
 
85
89
  view.updatePadding(
86
- top = bars.top, bottom = bars.bottom
90
+ top = bars.top,
91
+ bottom = bars.bottom
87
92
  )
88
-
89
93
  insets
90
94
  }
91
95
 
92
- runtime = ShellRuntime(
93
-
94
- this,
95
- webView,
96
- signerManager,
97
- permissionHandler = this
96
+ setupRuntime()
97
+ setupBackNavigation()
98
+ showLoader(true)
99
+ runtime.start()
98
100
 
99
- )
101
+ }
100
102
 
101
- onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
103
+ private fun setupBackNavigation() {
104
+ onBackPressedDispatcher.addCallback(
105
+ this,
106
+ object : OnBackPressedCallback(true) {
102
107
 
103
- override fun handleOnBackPressed() {
108
+ override fun handleOnBackPressed() {
104
109
 
105
- val webView = runtime.getWebview()
110
+ val currentWebView = runtime.getWebview()
106
111
 
107
- if (webView.canGoBack()) {
108
- webView.goBack()
109
- } else {
110
- finish()
112
+ if (currentWebView.canGoBack()) {
113
+ currentWebView.goBack()
114
+ } else {
115
+ finish()
116
+ }
111
117
  }
112
118
  }
113
-
114
- })
115
- loader.visibility = View.VISIBLE
116
- runtime.start()
119
+ )
117
120
  }
118
121
 
119
- private fun initViews() {
120
- container = findViewById(R.id.mainContainer)
121
- webView = findViewById(R.id.webView)
122
- loader = findViewById(R.id.loader)
122
+ private fun setupRuntime() {
123
+ runtime =
124
+ ShellRuntime(
125
+ this,
126
+ webView,
127
+ signerManager,
128
+ permissionHandler = this
129
+ )
123
130
  }
124
131
 
125
- override fun onDestroy() {
126
- runtime.stop()
127
- super.onDestroy()
132
+ private fun initSigner() {
133
+ signerManager =
134
+ SignerManager(
135
+ this,
136
+ amberLauncher
137
+ )
128
138
  }
129
139
 
140
+ private fun initViews() {
130
141
 
131
- private fun showSignerPicker() {
132
-
133
- val picker = SignerPickerBottomSheet { selectedType ->
134
-
135
- signerManager.setActiveType(selectedType)
136
-
137
- activeSigner = signerManager.getSigner()
138
-
139
- handleSignerSetup()
140
-
141
- }
142
-
143
- picker.show(
144
-
145
- supportFragmentManager,
146
-
147
- "SignerPicker"
148
-
149
- )
142
+ container =
143
+ findViewById(R.id.mainContainer)
144
+ webView =
145
+ findViewById(R.id.webView)
146
+ loader =
147
+ findViewById(R.id.loader)
150
148
 
149
+ permissionManager =
150
+ PermissionManager(this)
151
151
  }
152
152
 
153
153
  private fun handleSignerSetup() {
@@ -157,11 +157,24 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
157
157
  )
158
158
  }
159
159
 
160
+ override fun onDestroy() {
161
+
162
+ if (::runtime.isInitialized) {
163
+
164
+ runtime.stop()
165
+
166
+ }
167
+
168
+ super.onDestroy()
169
+
170
+ }
171
+
160
172
  override fun onRequestPermissionsResult(
161
173
  requestCode: Int,
162
174
  permissions: Array<out String>,
163
175
  grantResults: IntArray
164
176
  ) {
177
+
165
178
  super.onRequestPermissionsResult(
166
179
  requestCode,
167
180
  permissions,
@@ -176,33 +189,40 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
176
189
  override fun onPermissionRequest(
177
190
  request: PermissionRequest
178
191
  ) {
192
+
179
193
  permissionManager.request(
194
+
180
195
  Manifest.permission.CAMERA
196
+
181
197
  ) { granted ->
198
+
182
199
  if (granted) {
183
- request.grant(request.resources)
200
+
201
+ request.grant(
202
+ request.resources
203
+ )
184
204
  } else {
185
205
  request.deny()
186
206
  }
187
207
  }
188
208
  }
189
209
 
190
- private fun testPush() {
191
- permissionManager.requestNotificationPermission { granted ->
192
-
193
- if (granted) {
194
- Log.d(
195
- "PERMISSION",
196
- "Notifications granted"
197
- )
198
- } else {
199
- Log.d(
200
- "PERMISSION",
201
- "Notifications denied"
202
- )
203
- }
210
+ private fun showSignerPicker() {
204
211
 
205
- }
212
+ SignerPickerBottomSheet { selectedType ->
213
+ signerManager.setActiveType(selectedType)
214
+ handleSignerSetup()
215
+ }.show(
216
+ supportFragmentManager,
217
+ "SignerPicker"
218
+ )
219
+ }
206
220
 
221
+ private fun showLoader(
222
+ show: Boolean
223
+ ) {
224
+ loader.visibility =
225
+ if (show) View.VISIBLE
226
+ else View.GONE
207
227
  }
208
- }
228
+ }
@@ -1,7 +1,7 @@
1
1
  package com.pakstr.app
2
2
 
3
3
  import android.content.Context
4
- import android.util.Log
4
+ import com.pakstr.app.debug.AppDebugLogger
5
5
  import android.webkit.WebView
6
6
  import com.pakstr.app.permissions.interfaces.PermissionHandler
7
7
  import com.pakstr.app.signer.SignerManager
@@ -43,7 +43,7 @@ class ShellRuntime(
43
43
 
44
44
  if (!ok) {
45
45
 
46
- Log.e(
46
+ AppDebugLogger.log(
47
47
  "RUNTIME",
48
48
  "Server failed"
49
49
  )
@@ -84,7 +84,7 @@ class ShellRuntime(
84
84
  )
85
85
 
86
86
 
87
- Log.d(
87
+ AppDebugLogger.log(
88
88
  "SERVER",
89
89
  "Started OK"
90
90
  )
@@ -96,10 +96,9 @@ class ShellRuntime(
96
96
  } catch (e: Exception) {
97
97
 
98
98
 
99
- Log.e(
99
+ AppDebugLogger.log(
100
100
  "SERVER",
101
- "FAILED TO START",
102
- e
101
+ "Failed to start: ${e.message}"
103
102
  )
104
103
 
105
104
 
@@ -2,7 +2,6 @@ package com.pakstr.app
2
2
 
3
3
  import android.app.Activity
4
4
  import android.content.Context
5
- import android.util.Log
6
5
  import android.view.View
7
6
  import android.webkit.CookieManager
8
7
  import android.webkit.PermissionRequest
@@ -11,11 +10,22 @@ import android.webkit.WebResourceRequest
11
10
  import android.webkit.WebResourceResponse
12
11
  import android.webkit.WebView
13
12
  import android.webkit.WebViewClient
14
- import com.pakstr.app.R
13
+ import com.pakstr.app.debug.AppDebugLogger
15
14
  import com.pakstr.app.permissions.interfaces.PermissionHandler
16
15
  import com.pakstr.app.signer.NostrBridge
17
16
  import com.pakstr.app.signer.SignerManager
18
17
 
18
+ /**
19
+ * Controls WebView configuration and lifecycle.
20
+ *
21
+ * Responsibilities:
22
+ * - Configure secure WebView settings
23
+ * - Load local web application from NanoHTTPD server
24
+ * - Inject NIP-07 JavaScript provider
25
+ * - Expose Android signer bridge to JavaScript
26
+ * - Handle Web permissions
27
+ * - Cleanup WebView resources
28
+ */
19
29
  class WebViewController(
20
30
 
21
31
  private val context: Context,
@@ -35,6 +45,8 @@ class WebViewController(
35
45
  //webView.loadUrl("https://sigit.io")
36
46
  }
37
47
 
48
+ private var nip07Injected = false
49
+
38
50
  fun setup() {
39
51
  showLoader()
40
52
  setupWebView()
@@ -54,21 +66,30 @@ class WebViewController(
54
66
 
55
67
  allowFileAccess = false
56
68
  allowContentAccess = false
69
+
70
+ mediaPlaybackRequiresUserGesture = false
71
+ builtInZoomControls = false
72
+ displayZoomControls = false
73
+
74
+ mixedContentMode = android.webkit.WebSettings.MIXED_CONTENT_NEVER_ALLOW
57
75
  }
58
76
 
59
77
  // Standard clients are enough since overriding URL loading isn't required
60
78
  webViewClient = object : WebViewClient() {
61
79
 
62
- override fun onPageFinished(view: WebView?, url: String?) {
80
+ override fun onPageFinished(
81
+ view: WebView?,
82
+ url: String?
83
+ ) {
63
84
  super.onPageFinished(view, url)
64
- hideLoader()
65
- val js = context.assets.open("nip07.js")
66
85
 
67
- .bufferedReader()
86
+ hideLoader()
68
87
 
69
- .readText()
88
+ if (!nip07Injected) {
89
+ nip07Injected =
90
+ injectNip07()
70
91
 
71
- webView.evaluateJavascript(js, null)
92
+ }
72
93
  }
73
94
 
74
95
  override fun shouldInterceptRequest(
@@ -79,12 +100,18 @@ class WebViewController(
79
100
 
80
101
  ): WebResourceResponse? {
81
102
 
82
- val url = request?.url.toString()
83
-
84
- Log.d("WEB_ASSET", "REQUEST: $url")
85
-
86
103
  return super.shouldInterceptRequest(view, request)
87
104
  }
105
+
106
+ override fun shouldOverrideUrlLoading(
107
+ view: WebView?,
108
+ request: WebResourceRequest?
109
+ ): Boolean {
110
+ val url = request?.url ?: return true
111
+ return url.host != "127.0.0.1"
112
+ }
113
+
114
+
88
115
  }
89
116
  webChromeClient = object : WebChromeClient() {
90
117
 
@@ -96,13 +123,15 @@ class WebViewController(
96
123
  }
97
124
  }
98
125
 
126
+ // Exposes native Nostr signer APIs to the WebView.
127
+ // Available in JavaScript as window.AndroidBridge.
99
128
  webView.addJavascriptInterface(
100
129
 
101
130
  NostrBridge(
102
131
 
103
132
  signerManager = signerManager,
104
133
 
105
- webView = webView
134
+ webView = webView,
106
135
 
107
136
  ),
108
137
 
@@ -130,13 +159,10 @@ class WebViewController(
130
159
  fun destroy() {
131
160
  webView.stopLoading()
132
161
 
133
- webView.clearCache(true)
134
-
135
162
  webView.clearHistory()
136
-
137
- CookieManager.getInstance().removeAllCookies(null)
138
-
139
- CookieManager.getInstance().flush()
163
+ webView.clearCache(false)
164
+ CookieManager.getInstance()
165
+ .flush()
140
166
 
141
167
  webView.loadUrl("about:blank")
142
168
  webView.removeCallbacks(loadRunnable)
@@ -145,4 +171,27 @@ class WebViewController(
145
171
 
146
172
  webView.destroy()
147
173
  }
148
- }
174
+
175
+ private fun injectNip07(): Boolean {
176
+
177
+ return try {
178
+ val js =
179
+ context.assets
180
+ .open("nip07.js")
181
+ .bufferedReader()
182
+ .readText()
183
+
184
+ webView.evaluateJavascript(
185
+ js,
186
+ null
187
+ )
188
+ true
189
+ } catch (e: Exception) {
190
+ AppDebugLogger.log(
191
+ "WEBVIEW",
192
+ "Failed loading nip07.js: ${e.message}"
193
+ )
194
+ false
195
+ }
196
+ }
197
+ }