nappup 1.5.6 → 1.5.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 +1 -1
- package/src/helpers/app-metadata.js +28 -0
- package/src/index.js +71 -49
- package/src/services/nostr-signer.js +6 -3
package/package.json
CHANGED
|
@@ -9,6 +9,20 @@ export function extractHtmlMetadata (htmlContent) {
|
|
|
9
9
|
name = titleMatch[1].trim()
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
if (!name) {
|
|
13
|
+
const ogTitleRegex = /<meta\s+[^>]*(?:property|name)\s*=\s*["']og:title["'][^>]*content\s*=\s*["']([^"']+)["'][^>]*>/i
|
|
14
|
+
const ogTitleMatch = htmlContent.match(ogTitleRegex)
|
|
15
|
+
if (ogTitleMatch && ogTitleMatch[1]) {
|
|
16
|
+
name = ogTitleMatch[1].trim()
|
|
17
|
+
} else {
|
|
18
|
+
const altOgTitleRegex = /<meta\s+[^>]*content\s*=\s*["']([^"']+)["'][^>]*(?:property|name)\s*=\s*["']og:title["'][^>]*>/i
|
|
19
|
+
const altOgTitleMatch = htmlContent.match(altOgTitleRegex)
|
|
20
|
+
if (altOgTitleMatch && altOgTitleMatch[1]) {
|
|
21
|
+
name = altOgTitleMatch[1].trim()
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
const metaDescRegex = /<meta\s+[^>]*name\s*=\s*["']description["'][^>]*content\s*=\s*["']([^"']+)["'][^>]*>/i
|
|
13
27
|
const metaDescMatch = htmlContent.match(metaDescRegex)
|
|
14
28
|
if (metaDescMatch && metaDescMatch[1]) {
|
|
@@ -22,6 +36,20 @@ export function extractHtmlMetadata (htmlContent) {
|
|
|
22
36
|
description = altMetaDescMatch[1].trim()
|
|
23
37
|
}
|
|
24
38
|
}
|
|
39
|
+
|
|
40
|
+
if (!description) {
|
|
41
|
+
const ogDescRegex = /<meta\s+[^>]*(?:property|name)\s*=\s*["']og:description["'][^>]*content\s*=\s*["']([^"']+)["'][^>]*>/i
|
|
42
|
+
const ogDescMatch = htmlContent.match(ogDescRegex)
|
|
43
|
+
if (ogDescMatch && ogDescMatch[1]) {
|
|
44
|
+
description = ogDescMatch[1].trim()
|
|
45
|
+
} else {
|
|
46
|
+
const altOgDescRegex = /<meta\s+[^>]*content\s*=\s*["']([^"']+)["'][^>]*(?:property|name)\s*=\s*["']og:description["'][^>]*>/i
|
|
47
|
+
const altOgDescMatch = htmlContent.match(altOgDescRegex)
|
|
48
|
+
if (altOgDescMatch && altOgDescMatch[1]) {
|
|
49
|
+
description = altOgDescMatch[1].trim()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
25
53
|
} catch (_) {
|
|
26
54
|
// ignore
|
|
27
55
|
}
|
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
|
|
|
@@ -167,7 +167,7 @@ export async function toApp (fileList, nostrSigner, { log = () => {}, dTag, dTag
|
|
|
167
167
|
|
|
168
168
|
async function uploadBinaryDataChunks ({ nmmr, signer, filename, chunkLength, log, pause = 0, mimeType, shouldReupload = false }) {
|
|
169
169
|
const writeRelays = (await signer.getRelays()).write
|
|
170
|
-
const relays = [...new Set([...writeRelays, ...nappRelays])]
|
|
170
|
+
const relays = [...new Set([...writeRelays, ...nappRelays].map(r => r.trim().replace(/\/$/, '')))]
|
|
171
171
|
let chunkIndex = 0
|
|
172
172
|
for await (const chunk of nmmr.getChunks()) {
|
|
173
173
|
const dTag = chunk.x
|
|
@@ -182,6 +182,12 @@ async function uploadBinaryDataChunks ({ nmmr, signer, filename, chunkLength, lo
|
|
|
182
182
|
;({ pause } = (await throttledSendEvent(foundEvent, missingRelays, { pause, log, trailingPause: true, minSuccessfulRelays: 0 })))
|
|
183
183
|
continue
|
|
184
184
|
}
|
|
185
|
+
|
|
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
|
|
190
|
+
|
|
185
191
|
const binaryDataChunk = {
|
|
186
192
|
kind: 34600,
|
|
187
193
|
tags: [
|
|
@@ -192,7 +198,7 @@ async function uploadBinaryDataChunks ({ nmmr, signer, filename, chunkLength, lo
|
|
|
192
198
|
],
|
|
193
199
|
// These chunks already have the expected size of 51000 bytes
|
|
194
200
|
content: new Base93Encoder().update(chunk.contentBytes).getEncoded(),
|
|
195
|
-
created_at:
|
|
201
|
+
created_at: effectiveCreatedAt
|
|
196
202
|
}
|
|
197
203
|
|
|
198
204
|
const event = await signer.signEvent(binaryDataChunk)
|
|
@@ -268,7 +274,7 @@ async function throttledSendEvent (event, relays, {
|
|
|
268
274
|
}
|
|
269
275
|
|
|
270
276
|
async function getPreviousCtags (dTagValue, currentCtagValue, relays, signer) {
|
|
271
|
-
const targetRelays = [...new Set([...relays, ...nappRelays])]
|
|
277
|
+
const targetRelays = [...new Set([...relays, ...nappRelays].map(r => r.trim().replace(/\/$/, '')))]
|
|
272
278
|
const storedEvents = (await nostrRelays.getEvents({
|
|
273
279
|
kinds: [34600],
|
|
274
280
|
authors: [await signer.getPublicKey()],
|
|
@@ -327,61 +333,67 @@ async function uploadBundle ({ dTag, channel, fileMetadata, signer, pause = 0, s
|
|
|
327
333
|
...fileTags
|
|
328
334
|
]
|
|
329
335
|
|
|
330
|
-
const writeRelays = [...new Set([...(await signer.getRelays()).write, ...nappRelays])]
|
|
331
|
-
|
|
332
|
-
if (!shouldReupload) {
|
|
333
|
-
const events = (await nostrRelays.getEvents({
|
|
334
|
-
kinds: [kind],
|
|
335
|
-
authors: [await signer.getPublicKey()],
|
|
336
|
-
'#d': [dTag],
|
|
337
|
-
limit: 1
|
|
338
|
-
}, writeRelays)).result
|
|
339
|
-
|
|
340
|
-
if (events.length > 0) {
|
|
341
|
-
events.sort((a, b) => {
|
|
342
|
-
if (b.created_at !== a.created_at) return b.created_at - a.created_at
|
|
343
|
-
if (a.id < b.id) return -1
|
|
344
|
-
if (a.id > b.id) return 1
|
|
345
|
-
return 0
|
|
346
|
-
})
|
|
336
|
+
const writeRelays = [...new Set([...(await signer.getRelays()).write, ...nappRelays].map(r => r.trim().replace(/\/$/, '')))]
|
|
347
337
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
338
|
+
let mostRecentEvent
|
|
339
|
+
const events = (await nostrRelays.getEvents({
|
|
340
|
+
kinds: [kind],
|
|
341
|
+
authors: [await signer.getPublicKey()],
|
|
342
|
+
'#d': [dTag],
|
|
343
|
+
limit: 1
|
|
344
|
+
}, writeRelays)).result
|
|
345
|
+
|
|
346
|
+
if (events.length > 0) {
|
|
347
|
+
events.sort((a, b) => {
|
|
348
|
+
if (b.created_at !== a.created_at) return b.created_at - a.created_at
|
|
349
|
+
if (a.id < b.id) return -1
|
|
350
|
+
if (a.id > b.id) return 1
|
|
351
|
+
return 0
|
|
352
|
+
})
|
|
353
|
+
mostRecentEvent = events[0]
|
|
354
|
+
}
|
|
352
355
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
+
if (!shouldReupload && mostRecentEvent) {
|
|
357
|
+
const recentFileTags = mostRecentEvent.tags
|
|
358
|
+
.filter(t => t[0] === 'file' && t[2] !== '.well-known/napp.json')
|
|
359
|
+
.sort((a, b) => (a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0))
|
|
356
360
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
})
|
|
361
|
+
const currentFileTags = fileTags
|
|
362
|
+
.filter(t => t[2] !== '.well-known/napp.json')
|
|
363
|
+
.sort((a, b) => (a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0))
|
|
361
364
|
|
|
362
|
-
|
|
363
|
-
|
|
365
|
+
const isSame = currentFileTags.length === recentFileTags.length && currentFileTags.every((t, i) => {
|
|
366
|
+
const rt = recentFileTags[i]
|
|
367
|
+
return rt.length >= 4 && rt[1] === t[1] && rt[2] === t[2] && rt[3] === t[3]
|
|
368
|
+
})
|
|
364
369
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const missingRelays = writeRelays.filter(r => !coveredRelays.has(r))
|
|
370
|
+
if (isSame) {
|
|
371
|
+
log(`Bundle based on ${fileTags.length} files is up-to-date (id: ${mostRecentEvent.id} - created_at: ${new Date(mostRecentEvent.created_at * 1000).toISOString()})`)
|
|
368
372
|
|
|
369
|
-
|
|
373
|
+
const matchingEvents = events.filter(e => e.id === mostRecentEvent.id)
|
|
374
|
+
const coveredRelays = new Set(matchingEvents.map(e => e.meta?.relay).filter(Boolean))
|
|
375
|
+
const missingRelays = writeRelays.filter(r => !coveredRelays.has(r))
|
|
370
376
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
+
if (missingRelays.length === 0) return mostRecentEvent
|
|
378
|
+
|
|
379
|
+
// nostrRelays.getEvents currently doesn't tell us which event came from which relay,
|
|
380
|
+
// so we re-upload to all relays to ensure consistency
|
|
381
|
+
log(`Re-uploading existing bundle event to ${missingRelays.length} missing relays (out of ${writeRelays.length})`)
|
|
382
|
+
await throttledSendEvent(mostRecentEvent, missingRelays, { pause, trailingPause: true, log, minSuccessfulRelays: 0 })
|
|
383
|
+
return mostRecentEvent
|
|
377
384
|
}
|
|
378
385
|
}
|
|
379
386
|
|
|
387
|
+
const createdAt = Math.floor(Date.now() / 1000)
|
|
388
|
+
let effectiveCreatedAt = (mostRecentEvent && mostRecentEvent.created_at >= createdAt) ? mostRecentEvent.created_at + 1 : createdAt
|
|
389
|
+
const maxCreatedAt = createdAt + 172800 // 2 days ahead
|
|
390
|
+
if (effectiveCreatedAt > maxCreatedAt) effectiveCreatedAt = maxCreatedAt
|
|
391
|
+
|
|
380
392
|
const appBundle = {
|
|
381
393
|
kind,
|
|
382
394
|
tags,
|
|
383
395
|
content: '',
|
|
384
|
-
created_at:
|
|
396
|
+
created_at: effectiveCreatedAt
|
|
385
397
|
}
|
|
386
398
|
const event = await signer.signEvent(appBundle)
|
|
387
399
|
await throttledSendEvent(event, writeRelays, { pause, trailingPause: true, log })
|
|
@@ -416,11 +428,14 @@ async function maybeUploadStall ({
|
|
|
416
428
|
const hasMetadata = Boolean(trimmedName) || Boolean(trimmedSummary) || Boolean(iconRootHash) ||
|
|
417
429
|
Boolean(self) || (countries && countries.length > 0) || (categories && categories.length > 0) || (hashtags && hashtags.length > 0)
|
|
418
430
|
|
|
419
|
-
const relays = [...new Set([...writeRelays, ...nappRelays])]
|
|
431
|
+
const relays = [...new Set([...writeRelays, ...nappRelays].map(r => r.trim().replace(/\/$/, '')))]
|
|
420
432
|
|
|
421
433
|
const previousResult = await getPreviousStall(dTag, relays, signer, channel)
|
|
422
434
|
const previous = previousResult?.previous
|
|
423
|
-
if (!previous && !hasMetadata)
|
|
435
|
+
if (!previous && !hasMetadata) {
|
|
436
|
+
if (shouldReupload) log('Skipping stall event upload: No previous event found and no metadata provided.')
|
|
437
|
+
return { pause }
|
|
438
|
+
}
|
|
424
439
|
|
|
425
440
|
const publishStall = async (event) => {
|
|
426
441
|
const signedEvent = await signer.signEvent(event)
|
|
@@ -495,7 +510,10 @@ async function maybeUploadStall ({
|
|
|
495
510
|
if (isSummaryAuto) tags.push(['auto', 'summary'])
|
|
496
511
|
}
|
|
497
512
|
|
|
498
|
-
if (!hasIcon || !hasName)
|
|
513
|
+
if (!hasIcon || !hasName) {
|
|
514
|
+
log(`Skipping stall event creation: Missing required metadata.${!hasName ? ' Name is missing.' : ''}${!hasIcon ? ' Icon is missing.' : ''}`)
|
|
515
|
+
return { pause }
|
|
516
|
+
}
|
|
499
517
|
|
|
500
518
|
return await publishStall({
|
|
501
519
|
kind,
|
|
@@ -662,11 +680,15 @@ async function maybeUploadStall ({
|
|
|
662
680
|
return await throttledSendEvent(previous, missingRelays, { pause, log, trailingPause: true, minSuccessfulRelays: 0 })
|
|
663
681
|
}
|
|
664
682
|
|
|
683
|
+
let effectiveCreatedAt = (previous && previous.created_at >= createdAt) ? previous.created_at + 1 : createdAt
|
|
684
|
+
const maxCreatedAt = createdAt + 172800 // 2 days ahead
|
|
685
|
+
if (effectiveCreatedAt > maxCreatedAt) effectiveCreatedAt = maxCreatedAt
|
|
686
|
+
|
|
665
687
|
return await publishStall({
|
|
666
688
|
kind,
|
|
667
689
|
tags,
|
|
668
690
|
content: typeof previous.content === 'string' ? previous.content : '',
|
|
669
|
-
created_at:
|
|
691
|
+
created_at: effectiveCreatedAt
|
|
670
692
|
})
|
|
671
693
|
}
|
|
672
694
|
|
|
@@ -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
|
-
|
|
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
|
|