pakstr 0.3.1 → 0.3.3
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/android-template/app/src/main/java/com/pakstr/app/LocalServer.kt +153 -81
- package/android-template/app/src/main/java/com/pakstr/app/MainActivity.kt +9 -4
- package/android-template/app/src/main/java/com/pakstr/app/RuntimeConfig.kt +109 -11
- package/android-template/app/src/main/java/com/pakstr/app/WebViewController.kt +19 -0
- package/android-template/app/src/main/java/com/pakstr/app/crypto/NostrEventHasher.kt +8 -4
- package/android-template/app/src/main/java/com/pakstr/app/signer/AmberSigner.kt +54 -67
- package/android-template/app/src/main/java/com/pakstr/app/signer/NostrBridge.kt +0 -7
- package/package.json +1 -1
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
package com.pakstr.app
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
+
import android.util.Log
|
|
5
|
+
|
|
4
6
|
import android.webkit.MimeTypeMap
|
|
7
|
+
|
|
5
8
|
import fi.iki.elonen.NanoHTTPD
|
|
6
9
|
import java.io.IOException
|
|
7
|
-
import java.util.Locale
|
|
8
|
-
|
|
9
10
|
import java.net.HttpURLConnection
|
|
10
11
|
import java.net.URL
|
|
12
|
+
import java.util.Locale
|
|
11
13
|
|
|
12
14
|
class LocalServer(
|
|
13
15
|
|
|
@@ -17,158 +19,228 @@ class LocalServer(
|
|
|
17
19
|
|
|
18
20
|
) : NanoHTTPD(port) {
|
|
19
21
|
|
|
22
|
+
private val TAG = "LocalServer"
|
|
23
|
+
|
|
20
24
|
override fun serve(session: IHTTPSession?): Response {
|
|
21
|
-
// Fallback to root if URI is null
|
|
22
|
-
val uri =
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
val uri = (session?.uri ?: "/").replace("..", "")
|
|
25
27
|
|
|
26
|
-
.replace("..", "")
|
|
27
28
|
|
|
28
29
|
if (uri.startsWith("/api/")) {
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
if (!RuntimeConfig.apiEnabled(context)) {
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
return newFixedLengthResponse(
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
Response.Status.NOT_FOUND,
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
"application/json",
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
{
|
|
42
|
+
"error":"api_disabled"
|
|
43
|
+
}
|
|
44
|
+
""".trimIndent()
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return proxyApi(session, uri)
|
|
37
49
|
|
|
38
50
|
}
|
|
39
51
|
|
|
40
|
-
// Map the root URL to index.html, otherwise target the requested file in the assets folder
|
|
41
52
|
val filePath = when {
|
|
42
53
|
|
|
43
54
|
uri == "/" -> "www/index.html"
|
|
44
55
|
|
|
45
|
-
uri.contains(".") -> "www$uri"
|
|
56
|
+
uri.contains(".") -> "www$uri"
|
|
46
57
|
|
|
47
|
-
else -> "www/index.html"
|
|
58
|
+
else -> "www/index.html"
|
|
48
59
|
|
|
49
60
|
}
|
|
50
61
|
|
|
62
|
+
|
|
51
63
|
return try {
|
|
52
|
-
|
|
64
|
+
|
|
65
|
+
Log.d(
|
|
66
|
+
TAG, "Serving asset: $filePath"
|
|
67
|
+
)
|
|
68
|
+
|
|
53
69
|
val inputStream = context.assets.open(filePath)
|
|
54
70
|
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
newChunkedResponse(
|
|
72
|
+
Response.Status.OK, getMimeType(filePath), inputStream
|
|
73
|
+
)
|
|
74
|
+
|
|
57
75
|
} catch (e: IOException) {
|
|
58
|
-
|
|
59
|
-
|
|
76
|
+
|
|
77
|
+
Log.e(
|
|
78
|
+
TAG, "Failed loading asset: $filePath", e
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
newFixedLengthResponse(
|
|
82
|
+
Response.Status.NOT_FOUND, "text/plain", "404 Not Found"
|
|
83
|
+
)
|
|
60
84
|
}
|
|
61
85
|
}
|
|
62
86
|
|
|
63
|
-
/**
|
|
64
|
-
* Automatically detects the correct MIME type based on the file extension.
|
|
65
|
-
*/
|
|
66
87
|
private fun getMimeType(
|
|
67
88
|
file: String
|
|
68
89
|
): String {
|
|
69
90
|
|
|
70
|
-
val extension =
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
?.lowercase(Locale.ROOT)
|
|
74
|
-
?: ""
|
|
91
|
+
val extension = MimeTypeMap.getFileExtensionFromUrl(file)
|
|
92
|
+
?.lowercase(Locale.ROOT) ?: ""
|
|
93
|
+
|
|
75
94
|
|
|
76
95
|
return when (extension) {
|
|
77
|
-
"js" ->
|
|
78
|
-
"application/javascript"
|
|
79
96
|
|
|
80
|
-
"
|
|
81
|
-
"application/json"
|
|
97
|
+
"js" -> "application/javascript"
|
|
82
98
|
|
|
83
|
-
"
|
|
84
|
-
"text/css"
|
|
99
|
+
"json" -> "application/json"
|
|
85
100
|
|
|
86
|
-
"
|
|
87
|
-
"text/html"
|
|
101
|
+
"css" -> "text/css"
|
|
88
102
|
|
|
89
|
-
"
|
|
90
|
-
"image/svg+xml"
|
|
103
|
+
"html" -> "text/html"
|
|
91
104
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
?: "application/octet-stream"
|
|
105
|
+
"svg" -> "image/svg+xml"
|
|
106
|
+
|
|
107
|
+
else -> MimeTypeMap.getSingleton()
|
|
108
|
+
.getMimeTypeFromExtension(extension) ?: "application/octet-stream"
|
|
97
109
|
}
|
|
98
110
|
}
|
|
99
111
|
|
|
112
|
+
override fun start() {
|
|
113
|
+
|
|
114
|
+
super.start()
|
|
115
|
+
|
|
116
|
+
Log.d(
|
|
117
|
+
TAG, "🚀 LocalServer started on port $listeningPort"
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
100
121
|
private fun proxyApi(
|
|
101
|
-
session: IHTTPSession?,
|
|
102
|
-
uri: String
|
|
122
|
+
session: IHTTPSession?, uri: String
|
|
103
123
|
): Response {
|
|
104
124
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
RuntimeConfig.load(context)
|
|
125
|
+
return try {
|
|
108
126
|
|
|
109
|
-
|
|
127
|
+
val config = RuntimeConfig.load(context)
|
|
110
128
|
|
|
111
|
-
|
|
129
|
+
val apiBase = config.optString("apiBase")
|
|
112
130
|
|
|
131
|
+
if (apiBase.isBlank()) {
|
|
132
|
+
return newFixedLengthResponse(
|
|
113
133
|
Response.Status.INTERNAL_ERROR,
|
|
114
|
-
|
|
115
134
|
"application/json",
|
|
116
|
-
|
|
117
135
|
"""{"error":"apiBase missing"}"""
|
|
118
|
-
|
|
119
136
|
)
|
|
137
|
+
}
|
|
120
138
|
|
|
139
|
+
val finalUrl = apiBase.removeSuffix("/") + uri
|
|
121
140
|
|
|
122
|
-
|
|
141
|
+
val connection = URL(finalUrl).openConnection() as HttpURLConnection
|
|
142
|
+
connection.doInput = true
|
|
143
|
+
connection.instanceFollowRedirects = false
|
|
144
|
+
|
|
145
|
+
val method = session?.method?.name ?: "GET"
|
|
146
|
+
|
|
147
|
+
connection.requestMethod = method
|
|
148
|
+
|
|
149
|
+
connection.connectTimeout = 15000
|
|
150
|
+
connection.readTimeout = 30000
|
|
123
151
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
152
|
+
/*
|
|
153
|
+
* Forward headers
|
|
154
|
+
*/
|
|
155
|
+
|
|
156
|
+
session?.headers?.forEach { (key, value) ->
|
|
157
|
+
|
|
158
|
+
val skip = setOf(
|
|
159
|
+
"host", "content-length", "connection", "accept-encoding"
|
|
127
160
|
)
|
|
128
161
|
|
|
129
162
|
|
|
130
|
-
|
|
131
|
-
url.openConnection() as HttpURLConnection
|
|
163
|
+
if (!skip.contains(key.lowercase())) {
|
|
132
164
|
|
|
165
|
+
connection.setRequestProperty(
|
|
166
|
+
key, value
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
session?.headers?.get("authorization")
|
|
171
|
+
?.let { auth ->
|
|
172
|
+
connection.setRequestProperty(
|
|
173
|
+
"Authorization",
|
|
174
|
+
auth
|
|
175
|
+
)
|
|
176
|
+
}
|
|
133
177
|
|
|
134
|
-
connection.requestMethod =
|
|
135
|
-
session?.method?.name ?: "GET"
|
|
136
178
|
|
|
179
|
+
if (method == "POST" || method == "PUT" || method == "PATCH") {
|
|
137
180
|
|
|
138
|
-
|
|
139
|
-
connection.readTimeout = 10000
|
|
181
|
+
val files = HashMap<String, String>()
|
|
140
182
|
|
|
183
|
+
session?.parseBody(files)
|
|
141
184
|
|
|
142
|
-
|
|
143
|
-
connection.responseCode
|
|
185
|
+
val body = files["postData"] ?: ""
|
|
144
186
|
|
|
187
|
+
val bytes = body.toByteArray(Charsets.UTF_8)
|
|
145
188
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
connection.
|
|
149
|
-
|
|
150
|
-
|
|
189
|
+
connection.doOutput = true
|
|
190
|
+
session?.headers?.get("content-type")?.let {
|
|
191
|
+
connection.setRequestProperty(
|
|
192
|
+
"Content-Type",
|
|
193
|
+
it
|
|
194
|
+
)
|
|
195
|
+
}
|
|
151
196
|
|
|
197
|
+
connection.outputStream.use { output ->
|
|
152
198
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
199
|
+
output.write(bytes)
|
|
200
|
+
output.flush()
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
val code = connection.responseCode
|
|
205
|
+
|
|
206
|
+
val stream = if (code >= 400) connection.errorStream
|
|
207
|
+
else connection.inputStream
|
|
208
|
+
|
|
209
|
+
val body = stream?.bufferedReader()
|
|
210
|
+
?.use {
|
|
211
|
+
it.readText()
|
|
212
|
+
} ?: ""
|
|
159
213
|
|
|
214
|
+
val responseContentType =
|
|
215
|
+
connection.contentType ?: "application/json"
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
connection.disconnect()
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
return newFixedLengthResponse(
|
|
222
|
+
|
|
223
|
+
Response.Status.lookup(code) ?: Response.Status.OK,
|
|
224
|
+
|
|
225
|
+
responseContentType,
|
|
226
|
+
|
|
227
|
+
body
|
|
228
|
+
|
|
229
|
+
)
|
|
160
230
|
|
|
161
231
|
} catch (e: Exception) {
|
|
232
|
+
Log.e(
|
|
233
|
+
TAG,
|
|
234
|
+
"Proxy failed: ${e.javaClass.simpleName}"
|
|
235
|
+
)
|
|
236
|
+
return newFixedLengthResponse(
|
|
162
237
|
|
|
163
|
-
newFixedLengthResponse(
|
|
164
238
|
Response.Status.INTERNAL_ERROR,
|
|
239
|
+
|
|
165
240
|
"application/json",
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
"message":"${e.message}"
|
|
170
|
-
}
|
|
171
|
-
""".trimIndent()
|
|
241
|
+
|
|
242
|
+
"""{"error":"proxy_failed"}""".trimIndent()
|
|
243
|
+
|
|
172
244
|
)
|
|
173
245
|
}
|
|
174
246
|
}
|
|
@@ -18,17 +18,14 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|
|
18
18
|
import androidx.core.view.ViewCompat
|
|
19
19
|
import androidx.core.view.WindowInsetsCompat
|
|
20
20
|
import androidx.core.view.updatePadding
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
import com.pakstr.app.permissions.PermissionManager
|
|
23
23
|
import com.pakstr.app.permissions.interfaces.PermissionHandler
|
|
24
|
-
import com.pakstr.app.crypto.CryptoInitializer
|
|
25
|
-
import com.pakstr.app.debug.AppDebugLogger
|
|
26
24
|
|
|
27
25
|
import com.pakstr.app.signer.SignerManager
|
|
28
26
|
import com.pakstr.app.signer.SignerType
|
|
29
27
|
import com.pakstr.app.utils.AppPreferences
|
|
30
28
|
import com.pakstr.app.utils.SignerPickerBottomSheet
|
|
31
|
-
import kotlinx.coroutines.launch
|
|
32
29
|
|
|
33
30
|
class MainActivity : AppCompatActivity(), PermissionHandler {
|
|
34
31
|
|
|
@@ -41,6 +38,9 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
|
|
|
41
38
|
private lateinit var amberLauncher:
|
|
42
39
|
ActivityResultLauncher<Intent>
|
|
43
40
|
|
|
41
|
+
|
|
42
|
+
private val TAG = "ShellRuntime"
|
|
43
|
+
|
|
44
44
|
override fun onCreate(
|
|
45
45
|
savedInstanceState: Bundle?
|
|
46
46
|
) {
|
|
@@ -93,9 +93,11 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
|
|
|
93
93
|
insets
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
|
|
96
97
|
setupRuntime()
|
|
97
98
|
setupBackNavigation()
|
|
98
99
|
showLoader(true)
|
|
100
|
+
|
|
99
101
|
runtime.start()
|
|
100
102
|
|
|
101
103
|
}
|
|
@@ -143,11 +145,14 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
|
|
|
143
145
|
findViewById(R.id.mainContainer)
|
|
144
146
|
webView =
|
|
145
147
|
findViewById(R.id.webView)
|
|
148
|
+
|
|
146
149
|
loader =
|
|
147
150
|
findViewById(R.id.loader)
|
|
148
151
|
|
|
149
152
|
permissionManager =
|
|
150
153
|
PermissionManager(this)
|
|
154
|
+
|
|
155
|
+
WebView.setWebContentsDebuggingEnabled(true)
|
|
151
156
|
}
|
|
152
157
|
|
|
153
158
|
private fun handleSignerSetup() {
|
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
package com.pakstr.app
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
+
import android.util.Log
|
|
4
5
|
import org.json.JSONObject
|
|
5
6
|
|
|
6
7
|
object RuntimeConfig {
|
|
7
8
|
|
|
9
|
+
private const val TAG = "RuntimeConfig"
|
|
10
|
+
|
|
8
11
|
private var config: JSONObject? = null
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
fun load(context: Context): JSONObject {
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
config?.let {
|
|
17
|
+
Log.d(TAG, "Returning cached config: $it")
|
|
18
|
+
return it
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
|
|
18
22
|
config = try {
|
|
19
23
|
|
|
20
24
|
val input =
|
|
21
|
-
context.assets.open(
|
|
22
|
-
|
|
25
|
+
context.assets.open(
|
|
26
|
+
"www/pakstr-runtime.json"
|
|
27
|
+
)
|
|
23
28
|
|
|
24
29
|
val json =
|
|
25
30
|
input.bufferedReader()
|
|
@@ -28,24 +33,117 @@ object RuntimeConfig {
|
|
|
28
33
|
|
|
29
34
|
JSONObject(json)
|
|
30
35
|
|
|
31
|
-
|
|
32
36
|
} catch (e: Exception) {
|
|
33
37
|
|
|
38
|
+
Log.e(
|
|
39
|
+
TAG,
|
|
40
|
+
"Failed loading runtime config",
|
|
41
|
+
e
|
|
42
|
+
)
|
|
43
|
+
|
|
34
44
|
JSONObject()
|
|
35
45
|
|
|
36
46
|
}
|
|
37
47
|
|
|
38
48
|
|
|
49
|
+
Log.d(
|
|
50
|
+
TAG,
|
|
51
|
+
"FINAL CONFIG: $config"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
39
55
|
return config!!
|
|
40
56
|
}
|
|
41
57
|
|
|
42
58
|
|
|
43
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Returns backend API URL.
|
|
61
|
+
*
|
|
62
|
+
* Empty means:
|
|
63
|
+
* - Nostr-only mode
|
|
64
|
+
* - no REST API proxy
|
|
65
|
+
*/
|
|
66
|
+
fun apiBase(
|
|
67
|
+
context: Context
|
|
68
|
+
): String? {
|
|
69
|
+
|
|
70
|
+
val value =
|
|
71
|
+
load(context)
|
|
72
|
+
.optString(
|
|
73
|
+
"apiBase",
|
|
74
|
+
""
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
Log.d(
|
|
79
|
+
TAG,
|
|
80
|
+
"apiBase=$value"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
return value.takeIf {
|
|
85
|
+
it.isNotBlank()
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Determines if REST API proxy should be enabled.
|
|
92
|
+
*/
|
|
93
|
+
fun apiEnabled(
|
|
94
|
+
context: Context
|
|
95
|
+
): Boolean {
|
|
96
|
+
|
|
97
|
+
val enabled =
|
|
98
|
+
!apiBase(context).isNullOrBlank()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
Log.d(
|
|
102
|
+
TAG,
|
|
103
|
+
"apiEnabled=$enabled"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
return enabled
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Nostr relay URL.
|
|
113
|
+
*/
|
|
114
|
+
fun relayUrl(
|
|
115
|
+
context: Context
|
|
116
|
+
): String {
|
|
117
|
+
|
|
118
|
+
val relay =
|
|
119
|
+
load(context)
|
|
120
|
+
.optString(
|
|
121
|
+
"relayUrl",
|
|
122
|
+
""
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
Log.d(
|
|
127
|
+
TAG,
|
|
128
|
+
"relayUrl=$relay"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
return relay
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Clears cached config.
|
|
138
|
+
* Useful for testing after changing pakstr-runtime.json.
|
|
139
|
+
*/
|
|
140
|
+
fun clearCache() {
|
|
141
|
+
|
|
142
|
+
config = null
|
|
44
143
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
144
|
+
Log.d(
|
|
145
|
+
TAG,
|
|
146
|
+
"Config cache cleared"
|
|
147
|
+
)
|
|
50
148
|
}
|
|
51
149
|
}
|
|
@@ -49,6 +49,7 @@ class WebViewController(
|
|
|
49
49
|
|
|
50
50
|
fun setup() {
|
|
51
51
|
showLoader()
|
|
52
|
+
setupCookies()
|
|
52
53
|
setupWebView()
|
|
53
54
|
loadApp()
|
|
54
55
|
}
|
|
@@ -141,6 +142,24 @@ class WebViewController(
|
|
|
141
142
|
|
|
142
143
|
}
|
|
143
144
|
|
|
145
|
+
private fun setupCookies() {
|
|
146
|
+
|
|
147
|
+
val cookieManager = CookieManager.getInstance()
|
|
148
|
+
|
|
149
|
+
cookieManager.setAcceptCookie(true)
|
|
150
|
+
|
|
151
|
+
cookieManager.setAcceptThirdPartyCookies(
|
|
152
|
+
|
|
153
|
+
webView,
|
|
154
|
+
|
|
155
|
+
true
|
|
156
|
+
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
cookieManager.flush()
|
|
160
|
+
|
|
161
|
+
}
|
|
162
|
+
|
|
144
163
|
/**
|
|
145
164
|
* Loads the local server URL with a minor delay to ensure NanoHTTPD is fully bound.
|
|
146
165
|
*/
|
|
@@ -34,15 +34,20 @@ object NostrEventHasher {
|
|
|
34
34
|
event: JSONObject
|
|
35
35
|
): String {
|
|
36
36
|
|
|
37
|
+
val pubkey =
|
|
38
|
+
event.optString("pubkey")
|
|
39
|
+
|
|
40
|
+
require(pubkey.isNotBlank()) {
|
|
41
|
+
"Missing pubkey in nostr event: $event"
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
val serialized =
|
|
38
45
|
JSONArray()
|
|
39
46
|
.apply {
|
|
40
47
|
|
|
41
48
|
put(0)
|
|
42
49
|
|
|
43
|
-
put(
|
|
44
|
-
event.getString("pubkey")
|
|
45
|
-
)
|
|
50
|
+
put(pubkey)
|
|
46
51
|
|
|
47
52
|
put(
|
|
48
53
|
event.getLong("created_at")
|
|
@@ -67,7 +72,6 @@ object NostrEventHasher {
|
|
|
67
72
|
return sha256Hex(
|
|
68
73
|
serialized.toByteArray()
|
|
69
74
|
)
|
|
70
|
-
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
/**
|
|
@@ -118,79 +118,88 @@ class AmberSigner(
|
|
|
118
118
|
override suspend fun signEvent(
|
|
119
119
|
eventJson: String
|
|
120
120
|
): String =
|
|
121
|
-
|
|
122
121
|
withTimeout(30_000) {
|
|
123
122
|
|
|
123
|
+
val event = JSONObject(eventJson)
|
|
124
|
+
|
|
125
|
+
val currentPubkey = event.optString("pubkey")
|
|
126
|
+
|
|
127
|
+
if (currentPubkey.isBlank()) {
|
|
128
|
+
|
|
129
|
+
val amberPubkey = getPublicKey()
|
|
130
|
+
|
|
131
|
+
require(amberPubkey.isNotBlank()) {
|
|
132
|
+
|
|
133
|
+
"Amber returned empty pubkey"
|
|
134
|
+
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
event.put(
|
|
138
|
+
|
|
139
|
+
"pubkey",
|
|
140
|
+
|
|
141
|
+
amberPubkey
|
|
142
|
+
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
|
|
124
147
|
suspendCancellableCoroutine { continuation ->
|
|
125
148
|
|
|
126
|
-
|
|
149
|
+
AppDebugLogger.log(
|
|
150
|
+
"AMBER",
|
|
151
|
+
"Signing event request"
|
|
152
|
+
)
|
|
127
153
|
|
|
128
154
|
pendingCallback = { signature ->
|
|
129
155
|
|
|
130
156
|
if (signature.isEmpty()) {
|
|
131
157
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
continuation.resume("")
|
|
135
|
-
|
|
136
|
-
}
|
|
158
|
+
continuation.resume("")
|
|
137
159
|
|
|
138
160
|
} else {
|
|
139
161
|
|
|
140
162
|
val signedEvent =
|
|
141
|
-
|
|
142
163
|
NostrEventBuilder.addId(event)
|
|
143
164
|
.apply {
|
|
144
|
-
|
|
145
165
|
put(
|
|
146
|
-
"sig",
|
|
166
|
+
"sig",
|
|
167
|
+
signature
|
|
147
168
|
)
|
|
148
|
-
|
|
149
169
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
} else {
|
|
166
|
-
continuation.resume("")
|
|
167
|
-
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
val verified =
|
|
173
|
+
NostrEventVerifier.verify(
|
|
174
|
+
signedEvent
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
if (verified) {
|
|
179
|
+
continuation.resume(
|
|
180
|
+
signedEvent.toString()
|
|
181
|
+
)
|
|
182
|
+
} else {
|
|
183
|
+
continuation.resume("")
|
|
168
184
|
}
|
|
169
185
|
}
|
|
170
186
|
}
|
|
171
187
|
|
|
188
|
+
|
|
172
189
|
val intent = Intent(
|
|
173
|
-
Intent.ACTION_VIEW,
|
|
190
|
+
Intent.ACTION_VIEW,
|
|
191
|
+
Uri.parse(
|
|
174
192
|
"nostrsigner:${Uri.encode(event.toString())}"
|
|
175
193
|
)
|
|
176
194
|
).apply {
|
|
177
195
|
putExtra(
|
|
178
|
-
"type",
|
|
196
|
+
"type",
|
|
197
|
+
"sign_event"
|
|
179
198
|
)
|
|
180
199
|
}
|
|
181
|
-
try {
|
|
182
|
-
launcher.launch(intent)
|
|
183
|
-
|
|
184
|
-
} catch (_: ActivityNotFoundException) {
|
|
185
200
|
|
|
186
|
-
if (continuation.isActive) {
|
|
187
|
-
continuation.resume("")
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
201
|
|
|
191
|
-
|
|
192
|
-
pendingCallback = null
|
|
193
|
-
}
|
|
202
|
+
launcher.launch(intent)
|
|
194
203
|
}
|
|
195
204
|
}
|
|
196
205
|
|
|
@@ -434,13 +443,6 @@ class AmberSigner(
|
|
|
434
443
|
)
|
|
435
444
|
}
|
|
436
445
|
|
|
437
|
-
|
|
438
|
-
AppDebugLogger.log(
|
|
439
|
-
"NIP44",
|
|
440
|
-
"decrypt sender=$pubkey current=$pubkey length=${ciphertext.length}"
|
|
441
|
-
)
|
|
442
|
-
|
|
443
|
-
|
|
444
446
|
try {
|
|
445
447
|
|
|
446
448
|
launcher.launch(intent)
|
|
@@ -489,27 +491,12 @@ class AmberSigner(
|
|
|
489
491
|
?: result.data?.getStringExtra("content")
|
|
490
492
|
|
|
491
493
|
?: ""
|
|
492
|
-
|
|
493
494
|
AppDebugLogger.log(
|
|
494
|
-
|
|
495
|
-
"
|
|
496
|
-
|
|
497
|
-
value
|
|
498
|
-
|
|
495
|
+
"AMBER",
|
|
496
|
+
"Operation completed"
|
|
499
497
|
)
|
|
500
|
-
AppDebugLogger.log(
|
|
501
|
-
|
|
502
|
-
"AMBER_RESULT_KEYS",
|
|
503
|
-
|
|
504
|
-
result.data?.extras?.keySet()?.joinToString()
|
|
505
|
-
|
|
506
|
-
?: "NO_KEYS"
|
|
507
498
|
|
|
508
|
-
)
|
|
509
|
-
|
|
510
|
-
callback?.invoke(
|
|
511
|
-
value
|
|
512
|
-
)
|
|
499
|
+
callback?.invoke(value)
|
|
513
500
|
}
|
|
514
501
|
|
|
515
502
|
private suspend fun getCachedPublicKey(): String {
|
|
@@ -133,7 +133,6 @@ class NostrBridge(
|
|
|
133
133
|
val plaintext =
|
|
134
134
|
json.getString("plaintext")
|
|
135
135
|
|
|
136
|
-
|
|
137
136
|
val result =
|
|
138
137
|
signerManager
|
|
139
138
|
.getSigner()
|
|
@@ -171,7 +170,6 @@ class NostrBridge(
|
|
|
171
170
|
}
|
|
172
171
|
}
|
|
173
172
|
|
|
174
|
-
|
|
175
173
|
"nip04_decrypt" -> {
|
|
176
174
|
|
|
177
175
|
scope.launch {
|
|
@@ -182,7 +180,6 @@ class NostrBridge(
|
|
|
182
180
|
"NIP04 decrypt payload is empty"
|
|
183
181
|
}
|
|
184
182
|
|
|
185
|
-
|
|
186
183
|
val json =
|
|
187
184
|
JSONObject(payload)
|
|
188
185
|
|
|
@@ -192,7 +189,6 @@ class NostrBridge(
|
|
|
192
189
|
val ciphertext =
|
|
193
190
|
json.getString("ciphertext")
|
|
194
191
|
|
|
195
|
-
|
|
196
192
|
val result =
|
|
197
193
|
signerManager
|
|
198
194
|
.getSigner()
|
|
@@ -207,7 +203,6 @@ class NostrBridge(
|
|
|
207
203
|
result
|
|
208
204
|
)
|
|
209
205
|
|
|
210
|
-
|
|
211
206
|
} catch (e: SignerUnavailableException) {
|
|
212
207
|
|
|
213
208
|
resolve(
|
|
@@ -253,7 +248,6 @@ class NostrBridge(
|
|
|
253
248
|
result
|
|
254
249
|
)
|
|
255
250
|
|
|
256
|
-
|
|
257
251
|
} catch (e: Exception) {
|
|
258
252
|
|
|
259
253
|
resolve(
|
|
@@ -289,7 +283,6 @@ class NostrBridge(
|
|
|
289
283
|
result
|
|
290
284
|
)
|
|
291
285
|
|
|
292
|
-
|
|
293
286
|
} catch (e: Exception) {
|
|
294
287
|
|
|
295
288
|
resolve(
|