pakstr 0.1.0 → 0.2.1

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.
@@ -5,71 +5,112 @@
5
5
  const pending = new Map();
6
6
 
7
7
  function callNative(method, payload) {
8
-
9
8
  return new Promise((resolve, reject) => {
10
9
 
11
10
  const id = crypto.randomUUID();
12
11
 
13
- pending.set(
14
- id,
15
- {
16
- resolve,
17
- reject
18
- }
19
- );
12
+ pending.set(id, {
13
+ resolve,
14
+ reject
15
+ });
20
16
 
21
17
  window.AndroidBridge.call(
22
18
  method,
23
19
  id,
24
- payload
25
- ? JSON.stringify(payload)
26
- : ""
20
+ payload ? JSON.stringify(payload) : ""
27
21
  );
28
22
  });
29
23
  }
30
24
 
31
25
  window.__nostrResolve = (id, result) => {
32
26
 
33
- const p =
34
- pending.get(id);
35
-
27
+ const p = pending.get(id);
36
28
  if (!p) return;
37
29
 
38
30
  pending.delete(id);
39
31
 
40
32
  try {
41
-
42
- const parsed =
43
- JSON.parse(result);
33
+ const parsed = JSON.parse(result);
44
34
 
45
35
  if (parsed.error) {
46
36
  p.reject(parsed);
47
37
  return;
48
38
  }
39
+
49
40
  } catch (_) {
50
41
  // Normal string response
51
42
  }
43
+
52
44
  p.resolve(result);
53
45
  };
54
46
 
55
47
  window.nostr = {
56
48
 
57
49
  getPublicKey: async () => {
50
+ return await callNative("getPublicKey");
51
+ },
58
52
 
59
- return await callNative(
60
- "getPublicKey"
53
+ signEvent: async (event) => {
54
+
55
+ const result = await callNative(
56
+ "signEvent",
57
+ event
61
58
  );
59
+
60
+ return JSON.parse(result);
62
61
  },
63
62
 
64
- signEvent: async (event) => {
63
+ nip04: {
64
+
65
+ encrypt: async (pubkey, plaintext) => {
65
66
 
66
- const result =
67
- await callNative(
68
- "signEvent",
69
- event
67
+ return await callNative(
68
+ "nip04_encrypt",
69
+ {
70
+ pubkey,
71
+ plaintext
72
+ }
70
73
  );
74
+ },
75
+
76
+ decrypt: async (pubkey, ciphertext) => {
77
+
78
+ return await callNative(
79
+ "nip04_decrypt",
80
+ {
81
+ pubkey,
82
+ ciphertext
83
+ }
84
+ );
85
+ }
86
+
87
+ },
88
+
89
+ nip44: {
90
+
91
+ encrypt: async (pubkey, plaintext) => {
92
+
93
+ return await callNative(
94
+ "nip44_encrypt",
95
+ {
96
+ pubkey,
97
+ plaintext
98
+ }
99
+ );
100
+ },
101
+
102
+ decrypt: async (pubkey, ciphertext) => {
103
+
104
+ return await callNative(
105
+ "nip44_decrypt",
106
+ {
107
+ pubkey,
108
+ ciphertext
109
+ }
110
+ );
111
+ }
71
112
 
72
- return JSON.parse(result);
73
113
  }
74
114
  };
115
+
75
116
  })();
@@ -39,7 +39,6 @@
39
39
  Sign Event
40
40
  </button>
41
41
 
42
-
43
42
  <pre id="output">
44
43
  Waiting...
45
44
  </pre>
@@ -47,6 +46,7 @@ Waiting...
47
46
 
48
47
  <script>
49
48
 
49
+
50
50
  function log(data) {
51
51
 
52
52
  document.getElementById("output").innerText =
@@ -59,10 +59,16 @@ Waiting...
59
59
  }
60
60
 
61
61
 
62
- console.log(
63
- "Nostr provider:",
64
- window.nostr
65
- );
62
+ // Check provider after Pakstr injection
63
+ setTimeout(() => {
64
+
65
+ console.log(
66
+ "Nostr provider:",
67
+ window.nostr
68
+ );
69
+
70
+ }, 2000);
71
+
66
72
 
67
73
 
68
74
  async function getPublicKey() {
@@ -91,7 +97,8 @@ Waiting...
91
97
 
92
98
  pubkey,
93
99
 
94
- length: pubkey.length,
100
+ length:
101
+ pubkey.length,
95
102
 
96
103
  format:
97
104
  pubkey.startsWith("npub")
@@ -105,7 +112,8 @@ Waiting...
105
112
 
106
113
  log({
107
114
 
108
- error: e.message
115
+ error:
116
+ e.message
109
117
 
110
118
  });
111
119
 
@@ -115,102 +123,89 @@ Waiting...
115
123
 
116
124
 
117
125
 
118
- async function signEvent() {
119
-
120
- try {
121
-
122
- log("Signing event...");
123
-
124
- if (!window.nostr) {
125
-
126
- throw new Error(
127
126
 
128
- "Nostr provider not available"
129
-
130
- );
131
-
132
- }
133
-
134
- const pubkey =
135
-
136
- await window.nostr.getPublicKey();
137
-
138
- const event = {
127
+ async function signEvent() {
139
128
 
140
- pubkey: pubkey,
129
+ try {
141
130
 
142
- kind: 1,
131
+ log("Signing event...");
143
132
 
144
- content:
145
133
 
146
- "Hello from Pakstr NIP-07",
134
+ if (!window.nostr) {
147
135
 
148
- tags: [],
136
+ throw new Error(
137
+ "Nostr provider not available"
138
+ );
149
139
 
150
- created_at:
140
+ }
151
141
 
152
- Math.floor(
153
142
 
154
- Date.now() / 1000
143
+ const pubkey =
144
+ await window.nostr.getPublicKey();
155
145
 
156
- )
157
146
 
158
- };
147
+ const event = {
159
148
 
160
- console.log(
149
+ pubkey,
161
150
 
162
- "Unsigned event",
151
+ kind: 1,
163
152
 
164
- event
153
+ content:
154
+ "Hello from Pakstr NIP-07",
165
155
 
166
- );
156
+ tags: [],
167
157
 
168
- log({
158
+ created_at:
159
+ Math.floor(
160
+ Date.now() / 1000
161
+ )
169
162
 
170
- step: "sending event",
163
+ };
171
164
 
172
- event
173
165
 
174
- });
166
+ console.log(
167
+ "Unsigned event",
168
+ event
169
+ );
175
170
 
176
- const signedEvent =
177
171
 
178
- await window.nostr.signEvent(
172
+ const signedEvent =
173
+ await window.nostr.signEvent(
174
+ event
175
+ );
179
176
 
180
- event
181
177
 
178
+ console.log(
179
+ "Signed event",
180
+ signedEvent
182
181
  );
183
182
 
184
- console.log(
185
183
 
186
- "Signed event",
187
-
188
- signedEvent
189
-
190
- );
184
+ log({
191
185
 
192
- log({
186
+ success: true,
193
187
 
194
- success: true,
188
+ signedEvent
195
189
 
196
- signedEvent
190
+ });
197
191
 
198
- });
199
192
 
200
- } catch (e) {
193
+ } catch (e) {
201
194
 
202
- console.error(e);
195
+ console.error(e);
203
196
 
204
- log({
205
197
 
206
- error: e.message,
198
+ log({
207
199
 
208
- stack: e.stack
200
+ error:
201
+ e.message,
209
202
 
210
- });
203
+ stack:
204
+ e.stack
211
205
 
212
- }
206
+ });
213
207
 
208
+ }
214
209
  }
215
210
 
216
211
 
@@ -3,6 +3,7 @@ package com.pakstr.app
3
3
  import android.Manifest
4
4
  import android.content.Intent
5
5
  import android.os.Bundle
6
+ import android.util.Log
6
7
 
7
8
  import android.view.View
8
9
  import android.webkit.PermissionRequest
@@ -17,12 +18,17 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
17
18
  import androidx.core.view.ViewCompat
18
19
  import androidx.core.view.WindowInsetsCompat
19
20
  import androidx.core.view.updatePadding
21
+ import androidx.lifecycle.lifecycleScope
20
22
  import com.pakstr.app.permissions.PermissionManager
21
23
  import com.pakstr.app.permissions.interfaces.PermissionHandler
22
24
  import com.pakstr.app.crypto.CryptoInitializer
25
+ import com.pakstr.app.debug.AppDebugLogger
26
+
23
27
  import com.pakstr.app.signer.SignerManager
28
+ import com.pakstr.app.signer.SignerType
24
29
  import com.pakstr.app.utils.AppPreferences
25
30
  import com.pakstr.app.utils.SignerPickerBottomSheet
31
+ import kotlinx.coroutines.launch
26
32
 
27
33
  class MainActivity : AppCompatActivity(), PermissionHandler {
28
34
 
@@ -44,6 +50,12 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
44
50
 
45
51
  super.onCreate(savedInstanceState)
46
52
 
53
+ if (BuildConfig.DEBUG) {
54
+
55
+ WebView.setWebContentsDebuggingEnabled(true)
56
+
57
+ }
58
+
47
59
  setContentView(
48
60
  R.layout.activity_layout
49
61
  )
@@ -58,10 +70,12 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
58
70
  }
59
71
 
60
72
  initSigner()
61
-
62
- if (AppPreferences.getBoolean("isFirstRun", true)) {
63
- showSignerPicker()
64
- }
73
+ signerManager.setActiveType(
74
+ SignerType.AMBER
75
+ )
76
+ //if (AppPreferences.getBoolean("isFirstRun", true)) {
77
+ //showSignerPicker()
78
+ //}
65
79
 
66
80
  ViewCompat.setOnApplyWindowInsetsListener(
67
81
  container
@@ -144,8 +158,15 @@ class MainActivity : AppCompatActivity(), PermissionHandler {
144
158
  }
145
159
 
146
160
  override fun onDestroy() {
147
- runtime.stop()
161
+
162
+ if (::runtime.isInitialized) {
163
+
164
+ runtime.stop()
165
+
166
+ }
167
+
148
168
  super.onDestroy()
169
+
149
170
  }
150
171
 
151
172
  override fun onRequestPermissionsResult(
@@ -110,6 +110,8 @@ class WebViewController(
110
110
  val url = request?.url ?: return true
111
111
  return url.host != "127.0.0.1"
112
112
  }
113
+
114
+
113
115
  }
114
116
  webChromeClient = object : WebChromeClient() {
115
117
 
@@ -129,7 +131,7 @@ class WebViewController(
129
131
 
130
132
  signerManager = signerManager,
131
133
 
132
- webView = webView
134
+ webView = webView,
133
135
 
134
136
  ),
135
137
 
@@ -192,4 +194,4 @@ class WebViewController(
192
194
  false
193
195
  }
194
196
  }
195
- }
197
+ }
@@ -0,0 +1,33 @@
1
+ package com.pakstr.app.crypto
2
+
3
+ class Nip04Crypto : NostrEncryption {
4
+
5
+ override fun encrypt(
6
+
7
+ plaintext: String,
8
+
9
+ privateKey: ByteArray,
10
+
11
+ publicKey: ByteArray
12
+
13
+ ): String {
14
+
15
+ TODO()
16
+
17
+ }
18
+
19
+ override fun decrypt(
20
+
21
+ payload: String,
22
+
23
+ privateKey: ByteArray,
24
+
25
+ publicKey: ByteArray
26
+
27
+ ): String {
28
+
29
+ TODO()
30
+
31
+ }
32
+
33
+ }
@@ -0,0 +1,16 @@
1
+ package com.pakstr.app.crypto
2
+
3
+ interface NostrEncryption {
4
+
5
+ fun encrypt(
6
+ plaintext: String,
7
+ privateKey: ByteArray,
8
+ publicKey: ByteArray
9
+ ): String
10
+
11
+ fun decrypt(
12
+ payload: String,
13
+ privateKey: ByteArray,
14
+ publicKey: ByteArray
15
+ ): String
16
+ }
@@ -42,47 +42,77 @@ class AmberSigner(
42
42
  // Android intents return results asynchronously, so the callback is used
43
43
  // to resume the suspended coroutine when Amber responds.
44
44
  private var pendingCallback: ((String) -> Unit)? = null
45
+ private var cachedPubkey: String? = null
46
+ override suspend fun getPublicKey(): String {
45
47
 
46
- override suspend fun getPublicKey(): String = withTimeout(30_000) {
48
+ cachedPubkey?.let {
47
49
 
48
- suspendCancellableCoroutine { continuation ->
50
+ return it
49
51
 
50
- pendingCallback = { pubkey ->
52
+ }
53
+
54
+ return withTimeout(30_000) {
55
+
56
+ suspendCancellableCoroutine { continuation ->
57
+
58
+ pendingCallback = { pubkey ->
59
+
60
+ cachedPubkey = pubkey
61
+
62
+ if (continuation.isActive) {
63
+
64
+ continuation.resume(pubkey)
65
+
66
+ }
51
67
 
52
- if (continuation.isActive) {
53
- continuation.resume(pubkey)
54
68
  }
55
- }
56
69
 
57
- val intent = Intent(
58
- Intent.ACTION_VIEW
59
- ).apply {
70
+ val intent = Intent(
60
71
 
61
- data = Uri.parse(
62
- "nostrsigner:?type=get_public_key"
63
- )
72
+ Intent.ACTION_VIEW
64
73
 
65
- putExtra(
66
- "type", "get_public_key"
67
- )
74
+ ).apply {
68
75
 
69
- }
76
+ data = Uri.parse(
70
77
 
71
- try {
78
+ "nostrsigner:?type=get_public_key"
72
79
 
73
- launcher.launch(intent)
80
+ )
74
81
 
75
- } catch (_: ActivityNotFoundException) {
82
+ putExtra(
83
+
84
+ "type",
85
+
86
+ "get_public_key"
87
+
88
+ )
89
+
90
+ }
91
+
92
+ try {
93
+
94
+ launcher.launch(intent)
95
+
96
+ } catch (_: ActivityNotFoundException) {
97
+
98
+ if (continuation.isActive) {
99
+
100
+ continuation.resume("")
101
+
102
+ }
103
+
104
+ }
105
+
106
+ continuation.invokeOnCancellation {
107
+
108
+ pendingCallback = null
76
109
 
77
- if (continuation.isActive) {
78
- continuation.resume("")
79
110
  }
80
- }
81
111
 
82
- continuation.invokeOnCancellation {
83
- pendingCallback = null
84
112
  }
113
+
85
114
  }
115
+
86
116
  }
87
117
 
88
118
  override suspend fun signEvent(
@@ -165,23 +195,270 @@ class AmberSigner(
165
195
  }
166
196
 
167
197
  override suspend fun nip04Encrypt(
168
- pubkey: String, plaintext: String
169
- ): String {
198
+ pubkey: String,
199
+ plaintext: String
200
+ ): String =
201
+ withTimeout(30_000) {
170
202
 
171
- throw NotImplementedError(
172
- "NIP04 not implemented"
173
- )
174
- }
203
+ suspendCancellableCoroutine { continuation ->
204
+
205
+ pendingCallback = { encrypted ->
206
+
207
+ if (continuation.isActive) {
208
+ continuation.resume(encrypted)
209
+ }
210
+ }
211
+
212
+ val intent = Intent(
213
+ Intent.ACTION_VIEW
214
+ ).apply {
215
+
216
+ data = Uri.parse(
217
+ "nostrsigner:?type=nip04_encrypt" +
218
+ "&pubkey=$pubkey" +
219
+ "&plaintext=${Uri.encode(plaintext)}"
220
+ )
221
+
222
+ putExtra(
223
+ "type",
224
+ "nip04_encrypt"
225
+ )
226
+
227
+ putExtra(
228
+ "pubkey",
229
+ pubkey
230
+ )
231
+
232
+ putExtra(
233
+ "plaintext",
234
+ plaintext
235
+ )
236
+ }
237
+
238
+ try {
239
+
240
+ launcher.launch(intent)
241
+
242
+ } catch (_: ActivityNotFoundException) {
243
+
244
+ if (continuation.isActive) {
245
+ continuation.resume("")
246
+ }
247
+ }
248
+
249
+ continuation.invokeOnCancellation {
250
+ pendingCallback = null
251
+ }
252
+ }
253
+ }
175
254
 
176
255
  override suspend fun nip04Decrypt(
177
- pubkey: String, ciphertext: String
178
- ): String {
256
+ pubkey: String,
257
+ ciphertext: String
258
+ ): String = withTimeout(30_000) {
179
259
 
180
- throw NotImplementedError(
181
- "NIP04 not implemented"
182
- )
260
+ suspendCancellableCoroutine { continuation ->
261
+
262
+ pendingCallback = { result ->
263
+
264
+ if (continuation.isActive) {
265
+ continuation.resume(result)
266
+ }
267
+ }
268
+
269
+ val intent = Intent(
270
+ Intent.ACTION_VIEW
271
+ ).apply {
272
+
273
+ data = Uri.parse(
274
+ "nostrsigner:?type=nip04_decrypt" +
275
+ "&pubkey=$pubkey" +
276
+ "&message=${Uri.encode(ciphertext)}"
277
+ )
278
+
279
+ putExtra(
280
+ "type",
281
+ "nip04_decrypt"
282
+ )
283
+
284
+ putExtra(
285
+ "pubkey",
286
+ pubkey
287
+ )
288
+
289
+ putExtra(
290
+ "message",
291
+ ciphertext
292
+ )
293
+ }
294
+
295
+ try {
296
+
297
+ launcher.launch(intent)
298
+
299
+ } catch (_: ActivityNotFoundException) {
300
+
301
+ if (continuation.isActive) {
302
+ continuation.resume("")
303
+ }
304
+ }
305
+
306
+ continuation.invokeOnCancellation {
307
+ pendingCallback = null
308
+ }
309
+ }
183
310
  }
184
311
 
312
+ override suspend fun nip44Encrypt(
313
+
314
+ pubkey: String,
315
+
316
+ plaintext: String
317
+
318
+ ): String =
319
+
320
+ withTimeout(30_000) {
321
+
322
+ val currentUser = getCachedPublicKey()
323
+
324
+ suspendCancellableCoroutine { continuation ->
325
+
326
+ pendingCallback = { encrypted ->
327
+
328
+ if (continuation.isActive) {
329
+
330
+ continuation.resume(encrypted)
331
+
332
+ }
333
+
334
+ }
335
+
336
+ val intent = Intent(
337
+
338
+ Intent.ACTION_VIEW
339
+
340
+ ).apply {
341
+
342
+ data = Uri.parse(
343
+
344
+ "nostrsigner:${Uri.encode(plaintext)}"
345
+
346
+ )
347
+
348
+ putExtra(
349
+
350
+ "type",
351
+
352
+ "nip44_encrypt"
353
+
354
+ )
355
+
356
+ putExtra(
357
+
358
+ "pubkey",
359
+
360
+ pubkey
361
+
362
+ )
363
+
364
+ putExtra(
365
+
366
+ "current_user",
367
+
368
+ currentUser
369
+
370
+ )
371
+
372
+ }
373
+
374
+ try {
375
+
376
+ launcher.launch(intent)
377
+
378
+ } catch (_: ActivityNotFoundException) {
379
+
380
+ if (continuation.isActive) {
381
+
382
+ continuation.resume("")
383
+
384
+ }
385
+
386
+ }
387
+
388
+ continuation.invokeOnCancellation {
389
+
390
+ pendingCallback = null
391
+
392
+ }
393
+
394
+ }
395
+
396
+ }
397
+
398
+ override suspend fun nip44Decrypt(
399
+ pubkey: String,
400
+ ciphertext: String
401
+ ): String =
402
+ withTimeout(30_000) {
403
+ val currentUser = getCachedPublicKey()
404
+ suspendCancellableCoroutine { continuation ->
405
+
406
+ pendingCallback = { result ->
407
+
408
+ if (continuation.isActive) {
409
+ continuation.resume(result)
410
+ }
411
+ }
412
+
413
+ val intent = Intent(
414
+ Intent.ACTION_VIEW
415
+ ).apply {
416
+
417
+ data = Uri.parse(
418
+ "nostrsigner:${Uri.encode(ciphertext)}"
419
+ )
420
+
421
+ putExtra(
422
+ "type",
423
+ "nip44_decrypt"
424
+ )
425
+
426
+ putExtra(
427
+ "pubkey",
428
+ pubkey
429
+ )
430
+
431
+ putExtra(
432
+ "current_user",
433
+ currentUser
434
+ )
435
+ }
436
+
437
+
438
+ AppDebugLogger.log(
439
+ "NIP44",
440
+ "decrypt sender=$pubkey current=$pubkey length=${ciphertext.length}"
441
+ )
442
+
443
+
444
+ try {
445
+
446
+ launcher.launch(intent)
447
+
448
+ } catch (_: ActivityNotFoundException) {
449
+
450
+ if (continuation.isActive) {
451
+ continuation.resume("")
452
+ }
453
+ }
454
+
455
+
456
+ continuation.invokeOnCancellation {
457
+ pendingCallback = null
458
+ }
459
+ }
460
+ }
461
+
185
462
  fun onResult(
186
463
  result: ActivityResult
187
464
  ) {
@@ -200,10 +477,44 @@ class AmberSigner(
200
477
 
201
478
  val value =
202
479
 
203
- result.data?.getStringExtra("result") ?: result.data?.getStringExtra("signature") ?: ""
480
+ result.data?.getStringExtra("result")
481
+
482
+ ?: result.data?.getStringExtra("event")
483
+
484
+ ?: result.data?.getStringExtra("signature")
485
+
486
+ ?: result.data?.getStringExtra("plaintext")
487
+
488
+ ?: result.data?.getStringExtra("message")
489
+ ?: result.data?.getStringExtra("content")
490
+
491
+ ?: ""
492
+
493
+ AppDebugLogger.log(
494
+
495
+ "AMBER_RESULT",
496
+
497
+ value
498
+
499
+ )
500
+ AppDebugLogger.log(
501
+
502
+ "AMBER_RESULT_KEYS",
503
+
504
+ result.data?.extras?.keySet()?.joinToString()
505
+
506
+ ?: "NO_KEYS"
507
+
508
+ )
204
509
 
205
510
  callback?.invoke(
206
511
  value
207
512
  )
208
513
  }
514
+
515
+ private suspend fun getCachedPublicKey(): String {
516
+
517
+ return cachedPubkey ?: getPublicKey()
518
+
519
+ }
209
520
  }
@@ -25,4 +25,18 @@ class BunkerSigner(context: Context) : NostrSigner {
25
25
  ): String {
26
26
  TODO("Not yet implemented")
27
27
  }
28
+
29
+ override suspend fun nip44Encrypt(
30
+ pubkey: String,
31
+ plaintext: String
32
+ ): String {
33
+ TODO("Not yet implemented")
34
+ }
35
+
36
+ override suspend fun nip44Decrypt(
37
+ pubkey: String,
38
+ ciphertext: String
39
+ ): String {
40
+ TODO("Not yet implemented")
41
+ }
28
42
  }
@@ -28,4 +28,18 @@ class FakeSigner : NostrSigner {
28
28
 
29
29
  }
30
30
 
31
+ override suspend fun nip44Encrypt(
32
+ pubkey: String,
33
+ plaintext: String
34
+ ): String {
35
+ TODO("Not yet implemented")
36
+ }
37
+
38
+ override suspend fun nip44Decrypt(
39
+ pubkey: String,
40
+ ciphertext: String
41
+ ): String {
42
+ TODO("Not yet implemented")
43
+ }
44
+
31
45
  }
@@ -114,6 +114,195 @@ class NostrBridge(
114
114
  }
115
115
  }
116
116
 
117
+ "nip04_encrypt" -> {
118
+
119
+ scope.launch {
120
+
121
+ try {
122
+
123
+ require(!payload.isNullOrBlank()) {
124
+ "NIP04 encrypt payload is empty"
125
+ }
126
+
127
+ val json =
128
+ JSONObject(payload)
129
+
130
+ val pubkey =
131
+ json.getString("pubkey")
132
+
133
+ val plaintext =
134
+ json.getString("plaintext")
135
+
136
+
137
+ val result =
138
+ signerManager
139
+ .getSigner()
140
+ .nip04Encrypt(
141
+ pubkey,
142
+ plaintext
143
+ )
144
+
145
+
146
+ resolve(
147
+ callbackId,
148
+ result
149
+ )
150
+
151
+ } catch (e: SignerUnavailableException) {
152
+
153
+ resolve(
154
+ callbackId,
155
+ errorResponse(
156
+ "SIGNER_UNAVAILABLE",
157
+ e.message ?: "Signer unavailable"
158
+ )
159
+ )
160
+
161
+ } catch (e: Exception) {
162
+
163
+ resolve(
164
+ callbackId,
165
+ errorResponse(
166
+ "SIGNER_ERROR",
167
+ e.message ?: "Unknown signer error"
168
+ )
169
+ )
170
+ }
171
+ }
172
+ }
173
+
174
+
175
+ "nip04_decrypt" -> {
176
+
177
+ scope.launch {
178
+
179
+ try {
180
+
181
+ require(!payload.isNullOrBlank()) {
182
+ "NIP04 decrypt payload is empty"
183
+ }
184
+
185
+
186
+ val json =
187
+ JSONObject(payload)
188
+
189
+ val pubkey =
190
+ json.getString("pubkey")
191
+
192
+ val ciphertext =
193
+ json.getString("ciphertext")
194
+
195
+
196
+ val result =
197
+ signerManager
198
+ .getSigner()
199
+ .nip04Decrypt(
200
+ pubkey,
201
+ ciphertext
202
+ )
203
+
204
+
205
+ resolve(
206
+ callbackId,
207
+ result
208
+ )
209
+
210
+
211
+ } catch (e: SignerUnavailableException) {
212
+
213
+ resolve(
214
+ callbackId,
215
+ errorResponse(
216
+ "SIGNER_UNAVAILABLE",
217
+ e.message ?: "Signer unavailable"
218
+ )
219
+ )
220
+
221
+ } catch (e: Exception) {
222
+
223
+ resolve(
224
+ callbackId,
225
+ errorResponse(
226
+ "SIGNER_ERROR",
227
+ e.message ?: "Unknown signer error"
228
+ )
229
+ )
230
+ }
231
+ }
232
+ }
233
+
234
+ "nip44_encrypt" -> {
235
+
236
+ scope.launch {
237
+
238
+ try {
239
+
240
+ val json =
241
+ JSONObject(payload ?: "")
242
+
243
+ val result =
244
+ signerManager
245
+ .getSigner()
246
+ .nip44Encrypt(
247
+ json.getString("pubkey"),
248
+ json.getString("plaintext")
249
+ )
250
+
251
+ resolve(
252
+ callbackId,
253
+ result
254
+ )
255
+
256
+
257
+ } catch (e: Exception) {
258
+
259
+ resolve(
260
+ callbackId,
261
+ errorResponse(
262
+ "SIGNER_ERROR",
263
+ e.message ?: ""
264
+ )
265
+ )
266
+ }
267
+ }
268
+ }
269
+
270
+ "nip44_decrypt" -> {
271
+
272
+ scope.launch {
273
+
274
+ try {
275
+
276
+ val json =
277
+ JSONObject(payload ?: "")
278
+
279
+ val result =
280
+ signerManager
281
+ .getSigner()
282
+ .nip44Decrypt(
283
+ json.getString("pubkey"),
284
+ json.getString("ciphertext")
285
+ )
286
+
287
+ resolve(
288
+ callbackId,
289
+ result
290
+ )
291
+
292
+
293
+ } catch (e: Exception) {
294
+
295
+ resolve(
296
+ callbackId,
297
+ errorResponse(
298
+ "SIGNER_ERROR",
299
+ e.message ?: ""
300
+ )
301
+ )
302
+ }
303
+ }
304
+ }
305
+
117
306
  else -> {
118
307
 
119
308
  resolve(
@@ -44,6 +44,8 @@ class SignerManager(
44
44
 
45
45
  private var activeSigner: NostrSigner? = null
46
46
 
47
+ private var currentUserPubkey: String? = null
48
+
47
49
  fun getActiveType(): SignerType? {
48
50
 
49
51
  val value =
@@ -60,6 +62,26 @@ class SignerManager(
60
62
  }
61
63
  }
62
64
 
65
+ suspend fun getCurrentUserPubkey(): String {
66
+
67
+ currentUserPubkey?.let {
68
+
69
+ return it
70
+
71
+ }
72
+
73
+ val pk =
74
+
75
+ getSigner()
76
+
77
+ .getPublicKey()
78
+
79
+ currentUserPubkey = pk
80
+
81
+ return pk
82
+
83
+ }
84
+
63
85
  fun setActiveType(
64
86
  type: SignerType
65
87
  ) {
@@ -22,4 +22,14 @@ interface NostrSigner {
22
22
 
23
23
  ): String
24
24
 
25
+ suspend fun nip44Encrypt(
26
+ pubkey: String,
27
+ plaintext: String
28
+ ): String
29
+
30
+ suspend fun nip44Decrypt(
31
+ pubkey: String,
32
+ ciphertext: String
33
+ ): String
34
+
25
35
  }
@@ -23,10 +23,25 @@ function patchPermissions(androidRoot, manifest) {
23
23
  console.warn(`⚠️ Unknown permission: ${perm}`);
24
24
  continue;
25
25
  }
26
- const line = `<uses-permission android:name="${androidPermission}"/>`;
27
- if (!xml.includes(line)) {
28
- xml = xml.replace("<manifest", `<manifest\n ${line}`);
26
+ /**
27
+ * Check if permission already exists.
28
+ *
29
+ * Handles:
30
+ *
31
+ * <uses-permission android:name="android.permission.X"/>
32
+ *
33
+ * and
34
+ *
35
+ * <uses-permission android:name="android.permission.X" />
36
+ */
37
+ const exists = new RegExp(`<uses-permission[^>]+android:name="${androidPermission}"[^>]*/>`).test(xml);
38
+ if (exists) {
39
+ console.log(`✔ Permission already exists: ${androidPermission}`);
40
+ continue;
29
41
  }
42
+ const permissionLine = ` <uses-permission android:name="${androidPermission}" />`;
43
+ xml = xml.replace(/<manifest[^>]*>/, (match) => `${match}\n${permissionLine}`);
44
+ console.log(`➕ Added permission: ${androidPermission}`);
30
45
  }
31
46
  fs_1.default.writeFileSync(file, xml);
32
47
  console.log("🔐 Permissions patched");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pakstr",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "CLI for packaging Nostr web apps into Android APKs",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",