nappup 2.3.6 → 2.3.8

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/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "git+https://github.com/44billion/nappup.git"
7
7
  },
8
8
  "license": "MIT",
9
- "version": "2.3.6",
9
+ "version": "2.3.8",
10
10
  "description": "Nostr App Uploader",
11
11
  "type": "module",
12
12
  "scripts": {
@@ -22,7 +22,7 @@
22
22
  "@noble/hashes": "^2.0.0",
23
23
  "dotenv": "^17.2.0",
24
24
  "file-type": "^21.0.0",
25
- "libp2r2p": "^0.10.5",
25
+ "libp2r2p": "^0.10.6",
26
26
  "mime-types": "^3.0.1",
27
27
  "nmmr": "^2.0.0"
28
28
  },
package/src/errors.js CHANGED
@@ -12,6 +12,8 @@ export const NAPPUP_ERROR_CODES = Object.freeze({
12
12
  BLOSSOM_UPLOAD_FAILED: 'NAPPUP_BLOSSOM_UPLOAD_FAILED',
13
13
  IRFS_UPLOAD_FAILED: 'NAPPUP_IRFS_UPLOAD_FAILED',
14
14
  MANIFEST_UPLOAD_FAILED: 'NAPPUP_MANIFEST_UPLOAD_FAILED',
15
+ SIGNER_LOCKED: 'NAPPUP_SIGNER_LOCKED',
16
+ SIGNER_DENIED: 'NAPPUP_SIGNER_DENIED',
15
17
  UPLOAD_FAILED: 'NAPPUP_UPLOAD_FAILED'
16
18
  })
17
19
 
@@ -60,9 +62,51 @@ export class NappupError extends Error {
60
62
  }
61
63
  }
62
64
 
65
+ const SIGNER_LOCKED_PATTERNS = [
66
+ /^VAULT_LOCKED$/,
67
+ /vault (?:is )?locked/i,
68
+ /account (?:is )?locked/i,
69
+ /wallet (?:is )?locked/i
70
+ ]
71
+
72
+ const SIGNER_DENIED_PATTERNS = [
73
+ /permission denied/i,
74
+ /user (?:rejected|denied)/i,
75
+ /sign(?:ing)? request (?:rejected|denied)/i
76
+ ]
77
+
78
+ // Walks the cause chain looking for signer-level failures such as a locked
79
+ // vault or a rejected signing prompt. Returns a NAPPUP_* code or null.
80
+ export function classifySignerError (error) {
81
+ let current = error
82
+ for (let depth = 0; current && depth < 6; depth++) {
83
+ if (current.name === 'NotAllowedError' || current.code === 'DENIED_BY_USER') {
84
+ return NAPPUP_ERROR_CODES.SIGNER_DENIED
85
+ }
86
+ const message = typeof current.message === 'string' ? current.message : ''
87
+ if (SIGNER_LOCKED_PATTERNS.some(pattern => pattern.test(message))) {
88
+ return NAPPUP_ERROR_CODES.SIGNER_LOCKED
89
+ }
90
+ if (SIGNER_DENIED_PATTERNS.some(pattern => pattern.test(message))) {
91
+ return NAPPUP_ERROR_CODES.SIGNER_DENIED
92
+ }
93
+ current = current.cause
94
+ }
95
+ return null
96
+ }
97
+
63
98
  // Ensures every error crossing nappup's public API has a documented code.
64
99
  export function normalizeNappupError (error) {
65
- if (typeof error?.code === 'string' && error.code.startsWith('NAPPUP_')) return error
100
+ if (typeof error?.code === 'string' && error.code.startsWith('NAPPUP_')) {
101
+ const signerCode = classifySignerError(error)
102
+ if (signerCode && signerCode !== error.code) {
103
+ return new NappupError(signerCode, error.message, {
104
+ cause: error.cause,
105
+ details: error.details
106
+ })
107
+ }
108
+ return error
109
+ }
66
110
  if (error?.name === 'AbortError' || error?.code === 'ABORT_ERR') {
67
111
  return new NappupError(
68
112
  NAPPUP_ERROR_CODES.UPLOAD_CANCELLED,
@@ -71,7 +115,7 @@ export function normalizeNappupError (error) {
71
115
  )
72
116
  }
73
117
  return new NappupError(
74
- NAPPUP_ERROR_CODES.UPLOAD_FAILED,
118
+ classifySignerError(error) ?? NAPPUP_ERROR_CODES.UPLOAD_FAILED,
75
119
  error?.message || 'Upload failed',
76
120
  { cause: error }
77
121
  )
@@ -1,27 +1,18 @@
1
- import nostrRelays, { seedRelays, freeRelays } from '#services/nostr-relays.js'
1
+ import { freeRelays, getRelaysByPubkey } from 'libp2r2p/relay'
2
2
 
3
- export async function getRelays () {
3
+ export async function getRelays ({ _getRelaysByPubkey = getRelaysByPubkey } = {}) {
4
4
  if (this.relays) return this.relays
5
5
 
6
- const relayLists = (await nostrRelays.getEvents({ authors: [await this.getPublicKey()], kinds: [10002], limit: 1 }, seedRelays)).result
7
- const relayList = relayLists.sort((a, b) => b.created_at - a.created_at)[0]
8
- const rTags = (relayList?.tags ?? []).filter(v => v[0] === 'r' && /^wss?:\/\//.test(v[1]))
9
- if (rTags.length === 0) {
6
+ const pubkey = await this.getPublicKey()
7
+ const relaysByPubkey = await _getRelaysByPubkey([pubkey])
8
+ const relays = relaysByPubkey[pubkey]
9
+ if (!relays.read.length && !relays.write.length) {
10
10
  const defaults = freeRelays.slice(0, 2)
11
- return (this.relays = { read: defaults, write: defaults })
11
+ relays.read.push(...defaults)
12
+ relays.write.push(...defaults)
12
13
  }
13
-
14
- let keys
15
- const keyAllowList = { read: true, write: true }
16
- const relays = rTags.reduce((r, v) => {
17
- keys = [v[2]].filter(v2 => keyAllowList[v2])
18
- if (keys.length === 0) keys = ['read', 'write']
19
- keys.forEach(k => r[k].push(v[1].trim().replace(/\/$/, '')))
20
- return r
21
- }, { read: [], write: [] })
22
14
  for (const k in relays) {
23
15
  if (relays[k].length === 0) relays[k].push(...freeRelays)
24
- relays[k] = [...new Set(relays[k])]
25
16
  }
26
17
  return (this.relays = relays)
27
18
  }
package/src/index.js CHANGED
@@ -8,7 +8,7 @@ import { extractHtmlMetadata, findAppIcon, findIndexFile } from '#helpers/app-me
8
8
  import { getBlossomServers, healthCheckServers, uploadFilesToBlossom } from '#services/blossom-upload.js'
9
9
  import { uploadBinaryDataChunks } from '#services/irfs-upload.js'
10
10
  import { uploadSiteManifest } from '#services/site-manifest.js'
11
- import { NappupError, NAPPUP_ERROR_CODES, normalizeNappupError } from '#errors.js'
11
+ import { NappupError, NAPPUP_ERROR_CODES, normalizeNappupError, classifySignerError } from '#errors.js'
12
12
 
13
13
  export { NappupError, NAPPUP_ERROR_CODES } from '#errors.js'
14
14
 
@@ -301,15 +301,18 @@ async function publishApp (fileList, nostrSigner, {
301
301
  log
302
302
  })
303
303
  if (failedFiles.length) {
304
+ const signerError = failedFiles
305
+ .flatMap(failed => failed.errors ?? [])
306
+ .map(failed => failed.error)
307
+ .find(error => classifySignerError(error))
308
+ const details = {
309
+ failedFileCount: failedFiles.length,
310
+ filenames: failedFiles.map(failed => failed.filename).filter(Boolean)
311
+ }
304
312
  throw new NappupError(
305
313
  NAPPUP_ERROR_CODES.BLOSSOM_UPLOAD_FAILED,
306
314
  `${failedFiles.length} file(s) failed to upload to Blossom`,
307
- {
308
- details: {
309
- failedFileCount: failedFiles.length,
310
- filenames: failedFiles.map(failed => failed.filename).filter(Boolean)
311
- }
312
- }
315
+ signerError ? { cause: signerError, details } : { details }
313
316
  )
314
317
  }
315
318
 
@@ -2,6 +2,7 @@ import { sha256 } from '@noble/hashes/sha2.js'
2
2
  import nostrRelays from '#services/nostr-relays.js'
3
3
  import { bytesToBase16 } from '#helpers/base16.js'
4
4
  import { normalizeBlossomServerUrl } from 'libp2r2p/url'
5
+ import { classifySignerError } from '#errors.js'
5
6
 
6
7
  const DEFAULT_HEALTH_CHECK_TIMEOUT_MS = 5000
7
8
  const DEFAULT_EXISTENCE_CHECK_TIMEOUT_MS = 5000
@@ -149,6 +150,12 @@ async function uploadFileToServer (serverUrl, signer, file, fileHash, mimeType,
149
150
  const descriptor = await response.json()
150
151
  return { success: true, descriptor }
151
152
  } catch (err) {
153
+ if (classifySignerError(err)) {
154
+ // Signer-level failures (locked vault, rejected prompt) are not
155
+ // transient upload errors: retrying would just re-prompt or fail
156
+ // again, so surface them immediately.
157
+ return { success: false, error: err }
158
+ }
152
159
  if (attempt === maxRetries) {
153
160
  return { success: false, error: err }
154
161
  }