nappup 2.3.5 → 2.3.7
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/README.md +1 -1
- package/package.json +2 -2
- package/src/helpers/signer.js +8 -17
- package/src/index.js +2 -0
- package/src/services/blossom-upload.js +40 -7
package/README.md
CHANGED
|
@@ -137,7 +137,7 @@ await publishApp(fileList, signer, {
|
|
|
137
137
|
|
|
138
138
|
- **`fileList`** — a `FileList` or array of `File` objects (each needs `webkitRelativePath`).
|
|
139
139
|
- **`signer`** — a [NIP-07](https://github.com/nostr-protocol/nips/blob/master/07.md)-compatible signer. In the browser, `window.nostr` is used automatically if omitted.
|
|
140
|
-
- **`onEvent`** — optional callback that receives progress events with a `type` (`'init'`, `'file-uploaded'`, `'complete'`, `'error'`, …) and `progress` (0–100).
|
|
140
|
+
- **`onEvent`** — optional callback that receives progress events with a `type` (`'services-checking'`, `'init'`, `'file-uploaded'`, `'complete'`, `'error'`, …) and `progress` (0–100).
|
|
141
141
|
|
|
142
142
|
Rejected uploads use `NappupError`, with a stable code from
|
|
143
143
|
`NAPPUP_ERROR_CODES`. The original error is retained as `cause`, and some
|
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.
|
|
9
|
+
"version": "2.3.7",
|
|
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.
|
|
25
|
+
"libp2r2p": "^0.10.6",
|
|
26
26
|
"mime-types": "^3.0.1",
|
|
27
27
|
"nmmr": "^2.0.0"
|
|
28
28
|
},
|
package/src/helpers/signer.js
CHANGED
|
@@ -1,27 +1,18 @@
|
|
|
1
|
-
import
|
|
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
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
if (
|
|
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
|
-
|
|
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
|
@@ -43,6 +43,7 @@ export default async function (fileList, nostrSigner, opts = {}) {
|
|
|
43
43
|
* Every event has `type` (string) and `progress` (0–100 integer).
|
|
44
44
|
*
|
|
45
45
|
* Event types:
|
|
46
|
+
* 'services-checking' — Blossom preferences and server health are being checked
|
|
46
47
|
* 'init' — { totalFiles, totalSteps, dTag, relayCount, blossomCount }
|
|
47
48
|
* 'media-uploaded' — { mediaType: 'icon'|'key_art'|'screenshot', service: 'blossom'|'irfs'|null }
|
|
48
49
|
* 'file-uploaded' — { filename, service: 'blossom'|'irfs' }
|
|
@@ -182,6 +183,7 @@ async function publishApp (fileList, nostrSigner, {
|
|
|
182
183
|
let isExplicitIcon = false
|
|
183
184
|
let pause = 1000
|
|
184
185
|
|
|
186
|
+
emit({ type: 'services-checking' })
|
|
185
187
|
log('Checking for blossom servers...')
|
|
186
188
|
let blossomServerUrls = []
|
|
187
189
|
try {
|
|
@@ -3,6 +3,27 @@ import nostrRelays from '#services/nostr-relays.js'
|
|
|
3
3
|
import { bytesToBase16 } from '#helpers/base16.js'
|
|
4
4
|
import { normalizeBlossomServerUrl } from 'libp2r2p/url'
|
|
5
5
|
|
|
6
|
+
const DEFAULT_HEALTH_CHECK_TIMEOUT_MS = 5000
|
|
7
|
+
const DEFAULT_EXISTENCE_CHECK_TIMEOUT_MS = 5000
|
|
8
|
+
|
|
9
|
+
// Bounds browser fetches whose native network timeout can take minutes.
|
|
10
|
+
async function fetchWithTimeout (url, options, timeoutMs) {
|
|
11
|
+
const controller = new AbortController()
|
|
12
|
+
let timedOut = false
|
|
13
|
+
const timeoutId = setTimeout(() => {
|
|
14
|
+
timedOut = true
|
|
15
|
+
controller.abort()
|
|
16
|
+
}, timeoutMs)
|
|
17
|
+
try {
|
|
18
|
+
return await fetch(url, { ...options, signal: controller.signal })
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (timedOut) throw new Error(`request timed out after ${timeoutMs}ms`)
|
|
21
|
+
throw error
|
|
22
|
+
} finally {
|
|
23
|
+
clearTimeout(timeoutId)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
6
27
|
async function createAuthHeader (signer, modify) {
|
|
7
28
|
const now = Math.floor(Date.now() / 1000)
|
|
8
29
|
const event = {
|
|
@@ -43,13 +64,16 @@ export async function getBlossomServers (signer, writeRelays) {
|
|
|
43
64
|
/**
|
|
44
65
|
* Health-checks blossom servers with a simple HEAD request.
|
|
45
66
|
* A server is considered healthy if fetch resolves (any HTTP status).
|
|
46
|
-
*
|
|
67
|
+
* Network errors and timeouts mark a server as unreachable.
|
|
47
68
|
*/
|
|
48
|
-
export async function healthCheckServers (servers, signer, {
|
|
69
|
+
export async function healthCheckServers (servers, signer, {
|
|
70
|
+
log = () => {},
|
|
71
|
+
timeoutMs = DEFAULT_HEALTH_CHECK_TIMEOUT_MS
|
|
72
|
+
} = {}) {
|
|
49
73
|
const results = await Promise.allSettled(
|
|
50
74
|
servers.map(async (serverUrl) => {
|
|
51
75
|
const normalized = normalizeBlossomServerUrl(serverUrl)
|
|
52
|
-
await
|
|
76
|
+
await fetchWithTimeout(normalized, { method: 'HEAD', mode: 'no-cors' }, timeoutMs)
|
|
53
77
|
return normalized
|
|
54
78
|
})
|
|
55
79
|
)
|
|
@@ -86,9 +110,17 @@ export async function computeFileHash (file) {
|
|
|
86
110
|
async function uploadFileToServer (serverUrl, signer, file, fileHash, mimeType, { shouldReupload, log, maxRetries = 5 }) {
|
|
87
111
|
// Check if already uploaded
|
|
88
112
|
if (!shouldReupload) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
113
|
+
try {
|
|
114
|
+
const checkResponse = await fetchWithTimeout(
|
|
115
|
+
`${serverUrl}/${fileHash}`,
|
|
116
|
+
{ method: 'HEAD' },
|
|
117
|
+
DEFAULT_EXISTENCE_CHECK_TIMEOUT_MS
|
|
118
|
+
)
|
|
119
|
+
if (checkResponse.ok) {
|
|
120
|
+
return { success: true, alreadyExists: true }
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
log(`Could not check whether ${fileHash} exists on ${serverUrl}; uploading it anyway: ${error?.message ?? error}`)
|
|
92
124
|
}
|
|
93
125
|
}
|
|
94
126
|
|
|
@@ -184,7 +216,8 @@ export async function uploadFilesToBlossom ({
|
|
|
184
216
|
}
|
|
185
217
|
})
|
|
186
218
|
|
|
187
|
-
|
|
219
|
+
// Unexpected task errors indicate a programming failure and must reach the caller.
|
|
220
|
+
await Promise.all(serverTasks)
|
|
188
221
|
|
|
189
222
|
const uploadedFiles = []
|
|
190
223
|
const failedFiles = []
|