nappup 1.5.7 → 1.5.9

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": "1.5.7",
9
+ "version": "1.5.9",
10
10
  "description": "Nostr App Uploader",
11
11
  "type": "module",
12
12
  "scripts": {
package/src/index.js CHANGED
@@ -23,7 +23,7 @@ export async function toApp (fileList, nostrSigner, { log = () => {}, dTag, dTag
23
23
  if (typeof window !== 'undefined' && nostrSigner === window.nostr) {
24
24
  nostrSigner.getRelays = NostrSigner.prototype.getRelays
25
25
  }
26
- const writeRelays = (await nostrSigner.getRelays()).write
26
+ const writeRelays = [...new Set((await nostrSigner.getRelays()).write.map(r => r.trim().replace(/\/$/, '')))]
27
27
  log(`Found ${writeRelays.length} outbox relays for pubkey ${nostrSigner.getPublicKey()}:\n${writeRelays.join(', ')}`)
28
28
  if (writeRelays.length === 0) throw new Error('No outbox relays found')
29
29
 
@@ -166,14 +166,46 @@ export async function toApp (fileList, nostrSigner, { log = () => {}, dTag, dTag
166
166
  }
167
167
 
168
168
  async function uploadBinaryDataChunks ({ nmmr, signer, filename, chunkLength, log, pause = 0, mimeType, shouldReupload = false }) {
169
+ const pubkey = await signer.getPublicKey()
169
170
  const writeRelays = (await signer.getRelays()).write
170
- const relays = [...new Set([...writeRelays, ...nappRelays])]
171
+ const relays = [...new Set([...writeRelays, ...nappRelays].map(r => r.trim().replace(/\/$/, '')))]
172
+
173
+ // Find max stored created_at for this file's chunks
174
+ const rootHash = nmmr.getRoot()
175
+ const allCTags = Array.from({ length: chunkLength }, (_, i) => `${rootHash}:${i}`)
176
+ let maxStoredCreatedAt = 0
177
+
178
+ for (let i = 0; i < allCTags.length; i += 100) {
179
+ const batch = allCTags.slice(i, i + 100)
180
+ const storedEvents = (await nostrRelays.getEvents({
181
+ kinds: [34600],
182
+ authors: [pubkey],
183
+ '#c': batch,
184
+ limit: 1
185
+ }, relays)).result
186
+
187
+ if (storedEvents.length > 0) {
188
+ // Since results are desc sorted by created_at, the first one is the max
189
+ maxStoredCreatedAt = Math.max(maxStoredCreatedAt, storedEvents[0].created_at)
190
+ }
191
+ }
192
+
193
+ // Set initial created_at based on what's higher, maxStoredCreatedAt or current time
194
+ let createdAtCursor = (Math.max(maxStoredCreatedAt, Math.floor(Date.now() / 1000)) + chunkLength)
195
+
171
196
  let chunkIndex = 0
172
197
  for await (const chunk of nmmr.getChunks()) {
173
198
  const dTag = chunk.x
174
199
  const currentCtag = `${chunk.rootX}:${chunk.index}`
175
200
  const { otherCtags, hasCurrentCtag, foundEvent, missingRelays } = await getPreviousCtags(dTag, currentCtag, relays, signer)
176
201
  if (!shouldReupload && hasCurrentCtag) {
202
+ // Handling of partial uploads/resumes:
203
+ // If we are observing an existing chunk, we use its created_at to re-align our cursor
204
+ // for the next chunks (so next chunk will be this_chunk_time - 1)
205
+ if (foundEvent) {
206
+ createdAtCursor = foundEvent.created_at - 1
207
+ }
208
+
177
209
  if (missingRelays.length === 0) {
178
210
  log(`${filename}: Skipping chunk ${++chunkIndex} of ${chunkLength} (already uploaded)`)
179
211
  continue
@@ -183,10 +215,10 @@ async function uploadBinaryDataChunks ({ nmmr, signer, filename, chunkLength, lo
183
215
  continue
184
216
  }
185
217
 
186
- const createdAt = Math.floor(Date.now() / 1000)
187
- let effectiveCreatedAt = (foundEvent && foundEvent.created_at >= createdAt) ? foundEvent.created_at + 1 : createdAt
188
- const maxCreatedAt = createdAt + 172800 // 2 days ahead
189
- if (effectiveCreatedAt > maxCreatedAt) effectiveCreatedAt = maxCreatedAt
218
+ const effectiveCreatedAt = createdAtCursor
219
+ // The lower chunk index, the higher created_at must be
220
+ // for relays to serve chunks in the most efficient order
221
+ createdAtCursor--
190
222
 
191
223
  const binaryDataChunk = {
192
224
  kind: 34600,
@@ -274,7 +306,7 @@ async function throttledSendEvent (event, relays, {
274
306
  }
275
307
 
276
308
  async function getPreviousCtags (dTagValue, currentCtagValue, relays, signer) {
277
- const targetRelays = [...new Set([...relays, ...nappRelays])]
309
+ const targetRelays = [...new Set([...relays, ...nappRelays].map(r => r.trim().replace(/\/$/, '')))]
278
310
  const storedEvents = (await nostrRelays.getEvents({
279
311
  kinds: [34600],
280
312
  authors: [await signer.getPublicKey()],
@@ -333,7 +365,7 @@ async function uploadBundle ({ dTag, channel, fileMetadata, signer, pause = 0, s
333
365
  ...fileTags
334
366
  ]
335
367
 
336
- const writeRelays = [...new Set([...(await signer.getRelays()).write, ...nappRelays])]
368
+ const writeRelays = [...new Set([...(await signer.getRelays()).write, ...nappRelays].map(r => r.trim().replace(/\/$/, '')))]
337
369
 
338
370
  let mostRecentEvent
339
371
  const events = (await nostrRelays.getEvents({
@@ -428,7 +460,7 @@ async function maybeUploadStall ({
428
460
  const hasMetadata = Boolean(trimmedName) || Boolean(trimmedSummary) || Boolean(iconRootHash) ||
429
461
  Boolean(self) || (countries && countries.length > 0) || (categories && categories.length > 0) || (hashtags && hashtags.length > 0)
430
462
 
431
- const relays = [...new Set([...writeRelays, ...nappRelays])]
463
+ const relays = [...new Set([...writeRelays, ...nappRelays].map(r => r.trim().replace(/\/$/, '')))]
432
464
 
433
465
  const previousResult = await getPreviousStall(dTag, relays, signer, channel)
434
466
  const previous = previousResult?.previous
@@ -71,10 +71,13 @@ export default class NostrSigner {
71
71
  const relays = rTags.reduce((r, v) => {
72
72
  keys = [v[2]].filter(v2 => keyAllowList[v2])
73
73
  if (keys.length === 0) keys = ['read', 'write']
74
- keys.forEach(k => r[k].push(v[1]))
74
+ keys.forEach(k => r[k].push(v[1].trim().replace(/\/$/, '')))
75
75
  return r
76
76
  }, { read: [], write: [] })
77
- Object.values(relays).forEach(v => { v.length === 0 && v.push(...freeRelays) })
77
+ for (const k in relays) {
78
+ if (relays[k].length === 0) relays[k].push(...freeRelays)
79
+ relays[k] = [...new Set(relays[k])]
80
+ }
78
81
  return (this.relays = relays)
79
82
  }
80
83
 
@@ -91,7 +94,7 @@ export default class NostrSigner {
91
94
  content: '',
92
95
  created_at: Math.floor(Date.now() / 1000)
93
96
  })
94
- await nostrRelays.sendEvent(relayList, [...new Set([...seedRelays, ...this.relays.write])])
97
+ await nostrRelays.sendEvent(relayList, [...new Set([...seedRelays, ...this.relays.write].map(r => r.trim().replace(/\/$/, '')))])
95
98
  return this.relays
96
99
  }
97
100