pakstr 0.0.3 → 0.1.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 (27) 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 +47 -7
  7. package/android-template/app/src/main/assets/www/index.html +220 -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 +102 -103
  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 +65 -18
  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/NostrEventHasher.kt +100 -0
  16. package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEventVerifier.kt +54 -0
  17. package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrSignatureVerifier.kt +43 -0
  18. package/android-template/app/src/main/java/com/pakstr/app/crypto/Secp256k1.kt +63 -0
  19. package/android-template/app/src/main/java/com/pakstr/app/debug/AppDebugLogger.kt +21 -0
  20. package/android-template/app/src/main/java/com/pakstr/app/signer/AmberSigner.kt +142 -100
  21. package/android-template/app/src/main/java/com/pakstr/app/signer/NostrBridge.kt +115 -39
  22. package/android-template/app/src/main/java/com/pakstr/app/signer/NostrEventBuilder.kt +39 -0
  23. package/android-template/app/src/main/java/com/pakstr/app/signer/SignerManager.kt +83 -75
  24. package/android-template/app/src/main/java/com/pakstr/app/signer/SignerType.kt +0 -1
  25. package/android-template/app/src/main/java/com/pakstr/app/signer/exceptions/SignerUnavailableException.kt +5 -0
  26. package/android-template/app/src/main/res/values/strings.xml +1 -1
  27. package/package.json +1 -1
@@ -1,14 +1,16 @@
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
- import android.util.Log
6
+
6
7
  import android.view.View
7
8
  import android.webkit.PermissionRequest
8
9
  import android.webkit.WebView
9
10
  import android.widget.FrameLayout
10
11
  import androidx.activity.OnBackPressedCallback
11
12
  import androidx.activity.enableEdgeToEdge
13
+ import androidx.activity.result.ActivityResultLauncher
12
14
  import androidx.activity.result.contract.ActivityResultContracts
13
15
  import androidx.appcompat.app.AppCompatActivity
14
16
  import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
@@ -17,8 +19,8 @@ import androidx.core.view.WindowInsetsCompat
17
19
  import androidx.core.view.updatePadding
18
20
  import com.pakstr.app.permissions.PermissionManager
19
21
  import com.pakstr.app.permissions.interfaces.PermissionHandler
22
+ import com.pakstr.app.crypto.CryptoInitializer
20
23
  import com.pakstr.app.signer.SignerManager
21
- import com.pakstr.app.signer.interfaces.NostrSigner
22
24
  import com.pakstr.app.utils.AppPreferences
23
25
  import com.pakstr.app.utils.SignerPickerBottomSheet
24
26
 
@@ -27,127 +29,111 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
27
29
  private lateinit var runtime: ShellRuntime
28
30
  private lateinit var container: FrameLayout
29
31
  private lateinit var webView: WebView
30
-
31
32
  private lateinit var loader: View
32
-
33
33
  private lateinit var signerManager: SignerManager
34
-
35
- private var activeSigner: NostrSigner? = null
36
-
37
- private var pendingPermissionRequest: PermissionRequest? = null
38
-
39
34
  private lateinit var permissionManager: PermissionManager
35
+ private lateinit var amberLauncher:
36
+ ActivityResultLauncher<Intent>
40
37
 
41
- private val amberLauncher =
42
-
43
- registerForActivityResult(
44
-
45
- ActivityResultContracts.StartActivityForResult()
46
-
47
- ) { result ->
48
-
49
- signerManager.handleAmberResult(result)
50
-
51
- }
38
+ override fun onCreate(
39
+ savedInstanceState: Bundle?
40
+ ) {
52
41
 
53
- override fun onCreate(savedInstanceState: Bundle?) {
54
- val splashScreen = installSplashScreen()
42
+ installSplashScreen()
55
43
  enableEdgeToEdge()
56
44
 
57
45
  super.onCreate(savedInstanceState)
58
- setContentView(R.layout.activity_layout)
59
-
60
- permissionManager = PermissionManager(this)
61
46
 
62
- signerManager = SignerManager(
47
+ setContentView(
48
+ R.layout.activity_layout
49
+ )
63
50
 
64
- this
51
+ initViews()
65
52
 
66
- ) { intent ->
53
+ amberLauncher =
54
+ registerForActivityResult(
55
+ ActivityResultContracts.StartActivityForResult()
56
+ ) { result ->
57
+ signerManager.handleAmberResult(result)
58
+ }
67
59
 
68
- amberLauncher.launch(intent)
60
+ initSigner()
69
61
 
62
+ if (AppPreferences.getBoolean("isFirstRun", true)) {
63
+ showSignerPicker()
70
64
  }
71
65
 
72
- val isFirstRun = AppPreferences.getBoolean("isFirstRun", true)
73
-
74
- if (isFirstRun) {
75
- //showTwoOptionsPicker(prefs)
76
- showSignerPicker()
77
- //testPush()
78
- }
66
+ ViewCompat.setOnApplyWindowInsetsListener(
67
+ container
68
+ ) { view, insets ->
79
69
 
80
- initViews()
81
-
82
- ViewCompat.setOnApplyWindowInsetsListener(container) { view, insets ->
83
- val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
70
+ val bars =
71
+ insets.getInsets(
72
+ WindowInsetsCompat.Type.systemBars()
73
+ )
84
74
 
85
75
  view.updatePadding(
86
- top = bars.top, bottom = bars.bottom
76
+ top = bars.top,
77
+ bottom = bars.bottom
87
78
  )
88
-
89
79
  insets
90
80
  }
91
81
 
92
- runtime = ShellRuntime(
93
-
94
- this,
95
- webView,
96
- signerManager,
97
- permissionHandler = this
82
+ setupRuntime()
83
+ setupBackNavigation()
84
+ showLoader(true)
85
+ runtime.start()
98
86
 
99
- )
87
+ }
100
88
 
101
- onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
89
+ private fun setupBackNavigation() {
90
+ onBackPressedDispatcher.addCallback(
91
+ this,
92
+ object : OnBackPressedCallback(true) {
102
93
 
103
- override fun handleOnBackPressed() {
94
+ override fun handleOnBackPressed() {
104
95
 
105
- val webView = runtime.getWebview()
96
+ val currentWebView = runtime.getWebview()
106
97
 
107
- if (webView.canGoBack()) {
108
- webView.goBack()
109
- } else {
110
- finish()
98
+ if (currentWebView.canGoBack()) {
99
+ currentWebView.goBack()
100
+ } else {
101
+ finish()
102
+ }
111
103
  }
112
104
  }
113
-
114
- })
115
- loader.visibility = View.VISIBLE
116
- runtime.start()
105
+ )
117
106
  }
118
107
 
119
- private fun initViews() {
120
- container = findViewById(R.id.mainContainer)
121
- webView = findViewById(R.id.webView)
122
- loader = findViewById(R.id.loader)
108
+ private fun setupRuntime() {
109
+ runtime =
110
+ ShellRuntime(
111
+ this,
112
+ webView,
113
+ signerManager,
114
+ permissionHandler = this
115
+ )
123
116
  }
124
117
 
125
- override fun onDestroy() {
126
- runtime.stop()
127
- super.onDestroy()
118
+ private fun initSigner() {
119
+ signerManager =
120
+ SignerManager(
121
+ this,
122
+ amberLauncher
123
+ )
128
124
  }
129
125
 
126
+ private fun initViews() {
130
127
 
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
- )
128
+ container =
129
+ findViewById(R.id.mainContainer)
130
+ webView =
131
+ findViewById(R.id.webView)
132
+ loader =
133
+ findViewById(R.id.loader)
150
134
 
135
+ permissionManager =
136
+ PermissionManager(this)
151
137
  }
152
138
 
153
139
  private fun handleSignerSetup() {
@@ -157,11 +143,17 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
157
143
  )
158
144
  }
159
145
 
146
+ override fun onDestroy() {
147
+ runtime.stop()
148
+ super.onDestroy()
149
+ }
150
+
160
151
  override fun onRequestPermissionsResult(
161
152
  requestCode: Int,
162
153
  permissions: Array<out String>,
163
154
  grantResults: IntArray
164
155
  ) {
156
+
165
157
  super.onRequestPermissionsResult(
166
158
  requestCode,
167
159
  permissions,
@@ -176,33 +168,40 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
176
168
  override fun onPermissionRequest(
177
169
  request: PermissionRequest
178
170
  ) {
171
+
179
172
  permissionManager.request(
173
+
180
174
  Manifest.permission.CAMERA
175
+
181
176
  ) { granted ->
177
+
182
178
  if (granted) {
183
- request.grant(request.resources)
179
+
180
+ request.grant(
181
+ request.resources
182
+ )
184
183
  } else {
185
184
  request.deny()
186
185
  }
187
186
  }
188
187
  }
189
188
 
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
- }
189
+ private fun showSignerPicker() {
204
190
 
205
- }
191
+ SignerPickerBottomSheet { selectedType ->
192
+ signerManager.setActiveType(selectedType)
193
+ handleSignerSetup()
194
+ }.show(
195
+ supportFragmentManager,
196
+ "SignerPicker"
197
+ )
198
+ }
206
199
 
200
+ private fun showLoader(
201
+ show: Boolean
202
+ ) {
203
+ loader.visibility =
204
+ if (show) View.VISIBLE
205
+ else View.GONE
207
206
  }
208
- }
207
+ }
@@ -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,16 @@ 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
+ }
88
113
  }
89
114
  webChromeClient = object : WebChromeClient() {
90
115
 
@@ -96,6 +121,8 @@ class WebViewController(
96
121
  }
97
122
  }
98
123
 
124
+ // Exposes native Nostr signer APIs to the WebView.
125
+ // Available in JavaScript as window.AndroidBridge.
99
126
  webView.addJavascriptInterface(
100
127
 
101
128
  NostrBridge(
@@ -130,13 +157,10 @@ class WebViewController(
130
157
  fun destroy() {
131
158
  webView.stopLoading()
132
159
 
133
- webView.clearCache(true)
134
-
135
160
  webView.clearHistory()
136
-
137
- CookieManager.getInstance().removeAllCookies(null)
138
-
139
- CookieManager.getInstance().flush()
161
+ webView.clearCache(false)
162
+ CookieManager.getInstance()
163
+ .flush()
140
164
 
141
165
  webView.loadUrl("about:blank")
142
166
  webView.removeCallbacks(loadRunnable)
@@ -145,4 +169,27 @@ class WebViewController(
145
169
 
146
170
  webView.destroy()
147
171
  }
172
+
173
+ private fun injectNip07(): Boolean {
174
+
175
+ return try {
176
+ val js =
177
+ context.assets
178
+ .open("nip07.js")
179
+ .bufferedReader()
180
+ .readText()
181
+
182
+ webView.evaluateJavascript(
183
+ js,
184
+ null
185
+ )
186
+ true
187
+ } catch (e: Exception) {
188
+ AppDebugLogger.log(
189
+ "WEBVIEW",
190
+ "Failed loading nip07.js: ${e.message}"
191
+ )
192
+ false
193
+ }
194
+ }
148
195
  }