nappup 1.0.6 → 1.0.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/bin/nappup/helpers.js +4 -4
- package/bin/nappup/index.js +2 -2
- package/package.json +3 -2
- package/src/helpers/app.js +18 -13
- package/src/helpers/{byte.js → base16.js} +5 -4
- package/src/helpers/base36.js +68 -0
- package/src/helpers/base62.js +42 -39
- package/src/helpers/nip19.js +90 -0
- package/src/index.js +14 -14
- package/src/services/nostr-relays.js +2 -1
- package/src/services/nostr-signer.js +5 -5
- package/src/helpers/.gitkeep +0 -0
- package/src/services/.gitkeep +0 -0
package/bin/nappup/helpers.js
CHANGED
|
@@ -8,15 +8,15 @@ import { fileTypeFromFile } from 'file-type'
|
|
|
8
8
|
export function parseArgs (args) {
|
|
9
9
|
let dir = null
|
|
10
10
|
let sk = null
|
|
11
|
-
let
|
|
11
|
+
let dTag = null
|
|
12
12
|
let channel = null
|
|
13
13
|
|
|
14
14
|
for (let i = 0; i < args.length; i++) {
|
|
15
15
|
if (args[i] === '-s' && args[i + 1]) {
|
|
16
16
|
sk = args[i + 1]
|
|
17
17
|
i++ // Skip the next argument as it's part of -k
|
|
18
|
-
} else if (args[i] === '-
|
|
19
|
-
|
|
18
|
+
} else if (args[i] === '-d' && args[i + 1]) {
|
|
19
|
+
dTag = args[i + 1]
|
|
20
20
|
i++
|
|
21
21
|
} else if (args[i] === '--main' && channel === null) {
|
|
22
22
|
channel = 'main'
|
|
@@ -32,7 +32,7 @@ export function parseArgs (args) {
|
|
|
32
32
|
return {
|
|
33
33
|
dir: path.resolve(dir ?? '.'),
|
|
34
34
|
sk,
|
|
35
|
-
|
|
35
|
+
dTag,
|
|
36
36
|
channel: channel || 'main'
|
|
37
37
|
}
|
|
38
38
|
}
|
package/bin/nappup/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import toApp from '#index.js'
|
|
|
11
11
|
const args = parseArgs(process.argv.slice(2))
|
|
12
12
|
await confirmArgs(args)
|
|
13
13
|
|
|
14
|
-
const { dir, sk,
|
|
14
|
+
const { dir, sk, dTag, channel } = args
|
|
15
15
|
const fileList = await toFileList(getFiles(dir), dir)
|
|
16
16
|
|
|
17
|
-
await toApp(fileList, await NostrSigner.create(sk), { log: console.log.bind(console),
|
|
17
|
+
await toApp(fileList, await NostrSigner.create(sk), { log: console.log.bind(console), dTag, channel })
|
package/package.json
CHANGED
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
"url": "git+https://github.com/44billion/nappup.git"
|
|
7
7
|
},
|
|
8
8
|
"license": "GPL-3.0-or-later",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.8",
|
|
10
10
|
"description": "Nostr App Uploader",
|
|
11
11
|
"type": "module",
|
|
12
12
|
"scripts": {
|
|
13
|
-
"test": "node --test '
|
|
13
|
+
"test": "node --test 'tests/**/*.test.js'",
|
|
14
|
+
"test:only": "node --test --test-only 'tests/**/*.test.js'"
|
|
14
15
|
},
|
|
15
16
|
"bin": {
|
|
16
17
|
"nappup": "bin/nappup/index.js"
|
package/src/helpers/app.js
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { bytesToBase36 } from '#helpers/base36.js'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// 63 - (1<channel> + 5<b36loggeduserpkslug> 50<b36pk>)
|
|
4
|
+
// <b36loggeduserpkslug> pk chars at positions [7][17][27][37][47]
|
|
5
|
+
// to avoid vanity or pow colisions
|
|
6
|
+
export const NOSTR_APP_D_TAG_MAX_LENGTH = 7
|
|
4
7
|
|
|
5
|
-
export function
|
|
6
|
-
return isSubdomainSafe(string) && string.length <=
|
|
8
|
+
export function isNostrAppDTagSafe (string) {
|
|
9
|
+
return isSubdomainSafe(string) && string.length <= NOSTR_APP_D_TAG_MAX_LENGTH
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
function isSubdomainSafe (string) {
|
|
10
|
-
return /(?:^[
|
|
13
|
+
return /(?:^[a-z0-9]$)|(?:^(?!.*--)[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$)/.test(string)
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
export function
|
|
14
|
-
return toSubdomainSafe(string,
|
|
16
|
+
export function deriveNostrAppDTag (string) {
|
|
17
|
+
return toSubdomainSafe(string, NOSTR_APP_D_TAG_MAX_LENGTH)
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
async function toSubdomainSafe (string, maxStringLength) {
|
|
18
|
-
const byteLength =
|
|
21
|
+
const byteLength = baseMaxLengthToMaxSourceByteLength(maxStringLength, 36)
|
|
19
22
|
const bytes = (await toSha1(string)).slice(0, byteLength)
|
|
20
|
-
return
|
|
23
|
+
return bytesToBase36(bytes, maxStringLength)
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
async function toSha1 (string) {
|
|
@@ -25,12 +28,14 @@ async function toSha1 (string) {
|
|
|
25
28
|
return new Uint8Array(await crypto.subtle.digest('SHA-1', bytes))
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
// baseMaxLengthToMaxSourceByteLength(19, 62) === 14 byte length
|
|
32
|
+
// baseMaxLengthToMaxSourceByteLength(7, 36) === 4 byte length
|
|
33
|
+
function baseMaxLengthToMaxSourceByteLength (maxStringLength, base) {
|
|
34
|
+
if (!base) throw new Error('Which base?')
|
|
35
|
+
const baseLog = Math.log(base)
|
|
31
36
|
const log256 = Math.log(256)
|
|
32
37
|
|
|
33
|
-
const maxByteLength = (maxStringLength *
|
|
38
|
+
const maxByteLength = (maxStringLength * baseLog) / log256
|
|
34
39
|
|
|
35
40
|
return Math.floor(maxByteLength)
|
|
36
41
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
export function
|
|
1
|
+
export function bytesToBase16 (uint8aBytes) {
|
|
2
2
|
return Array.from(uint8aBytes).map(b => b.toString(16).padStart(2, '0')).join('')
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
export function
|
|
6
|
-
|
|
5
|
+
export function base16ToBytes (base16String) {
|
|
6
|
+
if (base16String.length % 2 !== 0) throw new Error('invalid hex: odd length')
|
|
7
|
+
const arr = new Uint8Array(base16String.length / 2) // create result array
|
|
7
8
|
for (let i = 0; i < arr.length; i++) {
|
|
8
9
|
const j = i * 2
|
|
9
|
-
const h =
|
|
10
|
+
const h = base16String.slice(j, j + 2)
|
|
10
11
|
const b = Number.parseInt(h, 16) // byte, created from string part
|
|
11
12
|
if (Number.isNaN(b) || b < 0) throw new Error('invalid hex')
|
|
12
13
|
arr[i] = b
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { base16ToBytes, bytesToBase16 } from '#helpers/base16.js'
|
|
2
|
+
import { base62ToBytes, bytesToBase62 } from '#helpers/base62.js'
|
|
3
|
+
|
|
4
|
+
export const BASE36_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'
|
|
5
|
+
const BASE = BigInt(BASE36_ALPHABET.length)
|
|
6
|
+
const LEADER = BASE36_ALPHABET[0]
|
|
7
|
+
const CHAR_MAP = new Map([...BASE36_ALPHABET].map((char, index) => [char, BigInt(index)]))
|
|
8
|
+
|
|
9
|
+
export function bytesToBase36 (bytes, padLength = 0) {
|
|
10
|
+
return base16ToBase36(bytesToBase16(bytes), padLength)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function base16ToBase36 (hex, padLength = 0) {
|
|
14
|
+
return bigIntToBase36(base16ToBigInt(hex), padLength)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function base62ToBase36 (base62str, padLength = 0) {
|
|
18
|
+
return bytesToBase36(base62ToBytes(base62str), padLength)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function base36ToBytes (base36str) {
|
|
22
|
+
return base16ToBytes(base36ToBase16(base36str))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function base36ToBase16 (base36str) {
|
|
26
|
+
return bigIntToBase16(base36ToBigInt(base36str))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function base36ToBase62 (base36str, padLength = 0) {
|
|
30
|
+
return bytesToBase62(base36ToBytes(base36str), padLength)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function base36ToBigInt (base36str) {
|
|
34
|
+
if (typeof base36str !== 'string') {
|
|
35
|
+
throw new Error('Input must be a string.')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let result = 0n
|
|
39
|
+
for (const char of base36str) {
|
|
40
|
+
const value = CHAR_MAP.get(char)
|
|
41
|
+
if (value === undefined) {
|
|
42
|
+
throw new Error(`Invalid character in Base36 string: ${char}`)
|
|
43
|
+
}
|
|
44
|
+
result = result * BASE + value
|
|
45
|
+
}
|
|
46
|
+
return result
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function bigIntToBase36 (num, padLength) {
|
|
50
|
+
if (typeof num !== 'bigint') throw new Error('Input must be a BigInt.')
|
|
51
|
+
if (num < 0n) throw new Error('Can\'t be signed BigInt')
|
|
52
|
+
|
|
53
|
+
return num.toString(36).padStart(padLength, LEADER)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function bigIntToBase16 (num) {
|
|
57
|
+
if (typeof num !== 'bigint') throw new Error('Input must be a BigInt.')
|
|
58
|
+
if (num < 0n) throw new Error('Can\'t be signed BigInt')
|
|
59
|
+
|
|
60
|
+
let hexString = num.toString(16)
|
|
61
|
+
if (hexString.length % 2 !== 0) hexString = `0${hexString}`
|
|
62
|
+
return hexString
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function base16ToBigInt (hex) {
|
|
66
|
+
if (typeof hex !== 'string') throw new Error('Input must be a string.')
|
|
67
|
+
return BigInt(`0x${hex}`)
|
|
68
|
+
}
|
package/src/helpers/base62.js
CHANGED
|
@@ -1,56 +1,59 @@
|
|
|
1
|
-
|
|
1
|
+
export const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
2
|
+
const BASE = BigInt(ALPHABET.length)
|
|
3
|
+
const LEADER = ALPHABET[0]
|
|
4
|
+
const CHAR_MAP = new Map([...ALPHABET].map((char, index) => [char, BigInt(index)]))
|
|
2
5
|
|
|
3
|
-
export function bytesToBase62 (bytes, padLength) {
|
|
4
|
-
|
|
5
|
-
return hexToBase62(hex, padLength)
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function hexToBase62 (hex, padLength) {
|
|
9
|
-
return bigIntToBase62(BigInt('0x' + hex), padLength)
|
|
10
|
-
}
|
|
6
|
+
export function bytesToBase62 (bytes, padLength = 0) {
|
|
7
|
+
if (bytes.length === 0) return ''.padStart(padLength, LEADER)
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
let num = 0n
|
|
10
|
+
for (const byte of bytes) {
|
|
11
|
+
num = (num << 8n) + BigInt(byte)
|
|
12
|
+
}
|
|
16
13
|
|
|
17
14
|
let result = ''
|
|
18
|
-
|
|
15
|
+
if (num === 0n) return LEADER.padStart(padLength, LEADER)
|
|
16
|
+
|
|
17
|
+
while (num > 0n) {
|
|
18
|
+
const remainder = num % BASE
|
|
19
|
+
result = ALPHABET[Number(remainder)] + result
|
|
20
|
+
num = num / BASE
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
for (const byte of bytes) {
|
|
24
|
+
if (byte !== 0) break
|
|
19
25
|
|
|
20
|
-
|
|
21
|
-
const remainder = currentNum % base
|
|
22
|
-
result = alphabet[Number(remainder)] + result
|
|
23
|
-
currentNum = currentNum / base
|
|
26
|
+
result = LEADER + result
|
|
24
27
|
}
|
|
25
|
-
|
|
28
|
+
|
|
29
|
+
return result.padStart(padLength, LEADER)
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
export function base62ToBytes (base62Str) {
|
|
29
|
-
|
|
30
|
-
return
|
|
31
|
-
}
|
|
33
|
+
if (typeof base62Str !== 'string') { throw new Error('base62ToBytes requires a string argument') }
|
|
34
|
+
if (base62Str.length === 0) return new Uint8Array()
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
36
|
+
let leadingZeros = 0
|
|
37
|
+
for (let i = 0; i < base62Str.length; i++) {
|
|
38
|
+
if (base62Str[i] !== LEADER) break
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const base = BigInt(alphabet.length)
|
|
41
|
-
// Create a lookup map for faster character value retrieval
|
|
42
|
-
const charMap = new Map(
|
|
43
|
-
[...alphabet].map((char, index) => [char, BigInt(index)])
|
|
44
|
-
)
|
|
40
|
+
leadingZeros++
|
|
41
|
+
}
|
|
45
42
|
|
|
46
|
-
let
|
|
43
|
+
let num = 0n
|
|
47
44
|
for (const char of base62Str) {
|
|
48
|
-
const value =
|
|
49
|
-
if (value === undefined) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
const value = CHAR_MAP.get(char)
|
|
46
|
+
if (value === undefined) { throw new Error(`Invalid character in Base62 string: ${char}`) }
|
|
47
|
+
num = num * BASE + value
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const bytes = []
|
|
51
|
+
while (num > 0n) {
|
|
52
|
+
bytes.unshift(Number(num & 0xffn))
|
|
53
|
+
num = num >> 8n
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
const result = new Uint8Array(leadingZeros + bytes.length)
|
|
57
|
+
result.set(bytes, leadingZeros)
|
|
55
58
|
return result
|
|
56
59
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { bytesToBase16, base16ToBytes } from '#helpers/base16.js'
|
|
2
|
+
import { bytesToBase62, base62ToBytes, ALPHABET as base62Alphabet } from '#helpers/base62.js'
|
|
3
|
+
import { isNostrAppDTagSafe } from '#helpers/app.js'
|
|
4
|
+
|
|
5
|
+
const MAX_SIZE = 5000
|
|
6
|
+
export const BASE62_ENTITY_REGEX = new RegExp(`^app-[${base62Alphabet}]{,${MAX_SIZE}}$`)
|
|
7
|
+
export const kindByChannel = {
|
|
8
|
+
main: 37448,
|
|
9
|
+
next: 37449,
|
|
10
|
+
draft: 37450
|
|
11
|
+
}
|
|
12
|
+
const channelEnum = Object.keys(kindByChannel)
|
|
13
|
+
const textEncoder = new TextEncoder()
|
|
14
|
+
const textDecoder = new TextDecoder()
|
|
15
|
+
|
|
16
|
+
export function appEncode (ref) {
|
|
17
|
+
if (!isNostrAppDTagSafe(ref.dTag)) { throw new Error('Invalid deduplication tag') }
|
|
18
|
+
const channelIndex = Object.entries(kindByChannel)
|
|
19
|
+
.findIndex(([k, v]) => ref.channel ? k === ref.channel : v === ref.kind)
|
|
20
|
+
if (channelIndex === -1) throw new Error('Wrong channel')
|
|
21
|
+
const tlv = toTlv([
|
|
22
|
+
[textEncoder.encode(ref.dTag)], // type 0 (the array index)
|
|
23
|
+
(ref.relays || []).map(url => textEncoder.encode(url)), // type 1
|
|
24
|
+
[base16ToBytes(ref.pubkey)], // type 2
|
|
25
|
+
[uintToBytes(channelIndex)] // type 3
|
|
26
|
+
])
|
|
27
|
+
const base62 = bytesToBase62(tlv)
|
|
28
|
+
return `app-${base62}`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function appDecode (entity) {
|
|
32
|
+
const [, base62] = entity.split('-')
|
|
33
|
+
const tlv = tlvToObj(base62ToBytes(base62))
|
|
34
|
+
if (!tlv[0]?.[0]) throw new Error('Missing deduplication tag')
|
|
35
|
+
if (!tlv[2]?.[0]) throw new Error('Missing author pubkey')
|
|
36
|
+
if (tlv[2][0].length !== 32) throw new Error('Author pubkey should be 32 bytes')
|
|
37
|
+
if (!tlv[3]?.[0]) throw new Error('Missing channel enum')
|
|
38
|
+
if (tlv[3][0].length !== 1) throw new Error('Channel enum should be 1 byte')
|
|
39
|
+
|
|
40
|
+
const channel = channelEnum[parseInt(tlv[3][0])]
|
|
41
|
+
return {
|
|
42
|
+
dTag: textDecoder.decode(tlv[0][0]),
|
|
43
|
+
pubkey: bytesToBase16(tlv[2][0]),
|
|
44
|
+
kind: kindByChannel[channel],
|
|
45
|
+
channel,
|
|
46
|
+
relays: tlv[1] ? tlv[1].map(url => textDecoder.decode(url)) : []
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Return shortest uint8Array size (not fixed size)
|
|
51
|
+
function uintToBytes (n, bytes = []) {
|
|
52
|
+
do { bytes.unshift(n & 255) } while ((n >>= 8) > 0)
|
|
53
|
+
return new Uint8Array(bytes)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function toTlv (tlvConfig) {
|
|
57
|
+
const arrays = []
|
|
58
|
+
tlvConfig
|
|
59
|
+
.map((v, i) => [i, v])
|
|
60
|
+
.reverse() // if the first type is 0, entity always starts with the '0' char
|
|
61
|
+
.forEach(([type, values]) => {
|
|
62
|
+
// just non-empty values will be included
|
|
63
|
+
values.forEach(v => {
|
|
64
|
+
if (v.length > 255) throw new Error('Value is too big')
|
|
65
|
+
const array = new Uint8Array(v.length + 2)
|
|
66
|
+
array.set([type], 0) // t
|
|
67
|
+
array.set([v.length], 1) // l
|
|
68
|
+
array.set(v, 2) // v
|
|
69
|
+
arrays.push(array)
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
return new Uint8Array(arrays.reduce((r, v) => [...r, ...v]))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// { 0: [t0v0], 1: [t1v0, t1v2], 3: [t3v0] }
|
|
76
|
+
function tlvToObj (tlv) {
|
|
77
|
+
const ret = {}
|
|
78
|
+
let rest = tlv
|
|
79
|
+
let t, l, v
|
|
80
|
+
while (rest.length > 0) {
|
|
81
|
+
t = rest[0]
|
|
82
|
+
l = rest[1]
|
|
83
|
+
v = rest.slice(2, 2 + l)
|
|
84
|
+
rest = rest.slice(2 + l)
|
|
85
|
+
if (v.length < l) throw new Error(`not enough data to read on TLV ${t}`)
|
|
86
|
+
ret[t] ??= []
|
|
87
|
+
ret[t].push(v)
|
|
88
|
+
}
|
|
89
|
+
return ret
|
|
90
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import NMMR from 'nmmr'
|
|
2
|
-
import {
|
|
2
|
+
import { appEncode } from '#helpers/nip19.js'
|
|
3
3
|
import Base122Encoder from '#services/base122-encoder.js'
|
|
4
4
|
import nostrRelays from '#services/nostr-relays.js'
|
|
5
5
|
import NostrSigner from '#services/nostr-signer.js'
|
|
6
6
|
import { streamToChunks } from '#helpers/stream.js'
|
|
7
|
-
import {
|
|
7
|
+
import { isNostrAppDTagSafe, deriveNostrAppDTag } from '#helpers/app.js'
|
|
8
8
|
|
|
9
9
|
export default async function (...args) {
|
|
10
10
|
try {
|
|
@@ -14,18 +14,18 @@ export default async function (...args) {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export async function toApp (fileList, nostrSigner, { log = () => {},
|
|
17
|
+
export async function toApp (fileList, nostrSigner, { log = () => {}, dTag, channel = 'main' } = {}) {
|
|
18
18
|
if (!nostrSigner && typeof window !== 'undefined') nostrSigner = window.nostr
|
|
19
19
|
if (!nostrSigner) throw new Error('No Nostr signer found')
|
|
20
20
|
if (typeof window !== 'undefined' && nostrSigner === window.nostr) {
|
|
21
21
|
nostrSigner.getRelays = NostrSigner.prototype.getRelays
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
if (typeof
|
|
25
|
-
if (!
|
|
24
|
+
if (typeof dTag === 'string') {
|
|
25
|
+
if (!isNostrAppDTagSafe(dTag)) throw new Error('dTag should be [A-Za-z0-9] with length ranging from 1 to 19')
|
|
26
26
|
} else {
|
|
27
|
-
|
|
28
|
-
if (!
|
|
27
|
+
dTag = fileList[0].webkitRelativePath.split('/')[0].trim()
|
|
28
|
+
if (!isNostrAppDTagSafe(dTag)) dTag = deriveNostrAppDTag(dTag || Math.random().toString(36))
|
|
29
29
|
}
|
|
30
30
|
let nmmr
|
|
31
31
|
const fileMetadata = []
|
|
@@ -53,16 +53,16 @@ export async function toApp (fileList, nostrSigner, { log = () => {}, appId, cha
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
log(`Uploading bundle #${
|
|
57
|
-
const bundle = await uploadBundle(
|
|
56
|
+
log(`Uploading bundle #${dTag}`)
|
|
57
|
+
const bundle = await uploadBundle(dTag, channel, fileMetadata, nostrSigner)
|
|
58
58
|
|
|
59
|
-
const
|
|
60
|
-
|
|
59
|
+
const appEntity = appEncode({
|
|
60
|
+
dTag: bundle.tags.find(v => v[0] === 'd')[1],
|
|
61
61
|
pubkey: bundle.pubkey,
|
|
62
62
|
relays: [],
|
|
63
63
|
kind: bundle.kind
|
|
64
64
|
})
|
|
65
|
-
log(`Visit at https://44billion.net/${
|
|
65
|
+
log(`Visit at https://44billion.net/${appEntity}`)
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
async function uploadBinaryDataChunks (nmmr, signer, { mimeType } = {}) {
|
|
@@ -116,7 +116,7 @@ async function getPreviousCtags (dTagValue, currentCtagValue, writeRelays, signe
|
|
|
116
116
|
})
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
async function uploadBundle (
|
|
119
|
+
async function uploadBundle (dTag, channel, fileMetadata, signer) {
|
|
120
120
|
const kind = {
|
|
121
121
|
main: 37448, // stable
|
|
122
122
|
next: 37449, // insider
|
|
@@ -125,7 +125,7 @@ async function uploadBundle (appId, channel, fileMetadata, signer) {
|
|
|
125
125
|
const appBundle = {
|
|
126
126
|
kind,
|
|
127
127
|
tags: [
|
|
128
|
-
['d',
|
|
128
|
+
['d', dTag],
|
|
129
129
|
...fileMetadata.map(v => ['file', v.rootHash, v.filename, v.mimeType])
|
|
130
130
|
],
|
|
131
131
|
content: '',
|
|
@@ -43,7 +43,8 @@ export class NostrRelays {
|
|
|
43
43
|
// Disconnect from a relay.
|
|
44
44
|
async disconnect (url) {
|
|
45
45
|
if (this.#relays.has(url)) {
|
|
46
|
-
|
|
46
|
+
const relay = this.#relays.get(url)
|
|
47
|
+
if (relay.ws.readyState < 2) await relay.close().catch(console.log)
|
|
47
48
|
this.#relays.delete(url)
|
|
48
49
|
clearTimeout(this.#relayTimeouts.get(url))
|
|
49
50
|
this.#relayTimeouts.delete(url)
|
|
@@ -5,7 +5,7 @@ import * as dotenv from 'dotenv'
|
|
|
5
5
|
import { getPublicKey, finalizeEvent } from 'nostr-tools/pure'
|
|
6
6
|
import { getConversationKey, encrypt, decrypt } from 'nostr-tools/nip44'
|
|
7
7
|
import nostrRelays, { seedRelays, freeRelays } from '#services/nostr-relays.js'
|
|
8
|
-
import {
|
|
8
|
+
import { bytesToBase16, base16ToBytes } from '#helpers/base16.js'
|
|
9
9
|
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
|
10
10
|
|
|
11
11
|
const dotenvPath = process.env.DOTENV_CONFIG_PATH ?? `${__dirname}/../../.env`
|
|
@@ -34,17 +34,17 @@ export default class NostrSigner {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
static async create (skHex) {
|
|
37
|
-
if (skHex) return new this(createToken,
|
|
37
|
+
if (skHex) return new this(createToken, base16ToBytes(skHex))
|
|
38
38
|
|
|
39
39
|
let skBytes
|
|
40
40
|
let isNewSk = false
|
|
41
41
|
if (process.env.NOSTR_SECRET_KEY) {
|
|
42
|
-
skBytes =
|
|
42
|
+
skBytes = base16ToBytes(process.env.NOSTR_SECRET_KEY)
|
|
43
43
|
} else {
|
|
44
44
|
isNewSk = true
|
|
45
45
|
skHex = generateSecretKey()
|
|
46
46
|
fs.appendFileSync(path.resolve(dotenvPath), `NOSTR_SECRET_KEY=${skHex}\n`)
|
|
47
|
-
skBytes =
|
|
47
|
+
skBytes = base16ToBytes(skHex)
|
|
48
48
|
}
|
|
49
49
|
const ret = new this(createToken, skBytes)
|
|
50
50
|
if (isNewSk) await ret.#initSk(skHex)
|
|
@@ -137,7 +137,7 @@ function generateSecretKey () {
|
|
|
137
137
|
const randomBytes = crypto.getRandomValues(new Uint8Array(40))
|
|
138
138
|
const B256 = 2n ** 256n // secp256k1 is short weierstrass curve
|
|
139
139
|
const N = B256 - 0x14551231950b75fc4402da1732fc9bebfn // curve (group) order
|
|
140
|
-
const bytesToNumber = b => BigInt('0x' + (
|
|
140
|
+
const bytesToNumber = b => BigInt('0x' + (bytesToBase16(b) || '0'))
|
|
141
141
|
const mod = (a, b) => { const r = a % b; return r >= 0n ? r : b + r } // mod division
|
|
142
142
|
const num = mod(bytesToNumber(randomBytes), N - 1n) + 1n // takes at least n+8 bytes
|
|
143
143
|
return num.toString(16).padStart(64, '0')
|
package/src/helpers/.gitkeep
DELETED
|
File without changes
|
package/src/services/.gitkeep
DELETED
|
File without changes
|