nappup 1.0.5 → 1.0.6
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.js +36 -0
- package/src/helpers/base62.js +56 -0
- package/src/helpers/byte.js +15 -0
- package/src/index.js +8 -2
- package/src/services/nostr-signer.js +1 -16
package/package.json
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { bytesToBase62 } from '#helpers/base62.js'
|
|
2
|
+
|
|
3
|
+
export const NOSTR_APP_ID_MAX_LENGTH = 19
|
|
4
|
+
|
|
5
|
+
export function isNostrAppIdSafe (string) {
|
|
6
|
+
return isSubdomainSafe(string) && string.length <= NOSTR_APP_ID_MAX_LENGTH
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isSubdomainSafe (string) {
|
|
10
|
+
return /(?:^[A-Za-z0-9]$)|(?:^(?!.*--)[A-Za-z0-9][A-Za-z0-9-]{0,63}[A-Za-z0-9]$)/.test(string)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function deriveNostrAppId (string) {
|
|
14
|
+
return toSubdomainSafe(string, NOSTR_APP_ID_MAX_LENGTH)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function toSubdomainSafe (string, maxStringLength) {
|
|
18
|
+
const byteLength = base62MaxLengthToMaxSourceByteLength(maxStringLength)
|
|
19
|
+
const bytes = (await toSha1(string)).slice(0, byteLength)
|
|
20
|
+
return bytesToBase62(bytes, maxStringLength)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function toSha1 (string) {
|
|
24
|
+
const bytes = new TextEncoder().encode(string)
|
|
25
|
+
return new Uint8Array(await crypto.subtle.digest('SHA-1', bytes))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// base62MaxLengthToMaxSourceByteLength(19) === 14 byte length
|
|
29
|
+
function base62MaxLengthToMaxSourceByteLength (maxStringLength) {
|
|
30
|
+
const log62 = Math.log(62)
|
|
31
|
+
const log256 = Math.log(256)
|
|
32
|
+
|
|
33
|
+
const maxByteLength = (maxStringLength * log62) / log256
|
|
34
|
+
|
|
35
|
+
return Math.floor(maxByteLength)
|
|
36
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { bytesToHex, hexToBytes } from '#helpers/byte.js'
|
|
2
|
+
|
|
3
|
+
export function bytesToBase62 (bytes, padLength) {
|
|
4
|
+
const hex = bytesToHex(bytes)
|
|
5
|
+
return hexToBase62(hex, padLength)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function hexToBase62 (hex, padLength) {
|
|
9
|
+
return bigIntToBase62(BigInt('0x' + hex), padLength)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function bigIntToBase62 (num, padLength = 0) {
|
|
13
|
+
const alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
14
|
+
const base = BigInt(alphabet.length)
|
|
15
|
+
if (num === 0n) return alphabet[0].padStart(padLength, alphabet[0])
|
|
16
|
+
|
|
17
|
+
let result = ''
|
|
18
|
+
let currentNum = num
|
|
19
|
+
|
|
20
|
+
while (currentNum > 0n) {
|
|
21
|
+
const remainder = currentNum % base
|
|
22
|
+
result = alphabet[Number(remainder)] + result
|
|
23
|
+
currentNum = currentNum / base
|
|
24
|
+
}
|
|
25
|
+
return result.padStart(padLength, alphabet[0])
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function base62ToBytes (base62Str) {
|
|
29
|
+
const hexString = base62ToHex(base62Str)
|
|
30
|
+
return hexToBytes(hexString)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function base62ToHex (base62Str, padLength = 64 /* nostr hex key */) {
|
|
34
|
+
const bigIntValue = base62ToBigInt(base62Str)
|
|
35
|
+
return bigIntValue.toString(16).padStart(padLength, '0')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function base62ToBigInt (base62Str) {
|
|
39
|
+
const alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
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
|
+
)
|
|
45
|
+
|
|
46
|
+
let result = 0n
|
|
47
|
+
for (const char of base62Str) {
|
|
48
|
+
const value = charMap.get(char)
|
|
49
|
+
if (value === undefined) {
|
|
50
|
+
throw new Error(`Invalid character in Base62 string: ${char}`)
|
|
51
|
+
}
|
|
52
|
+
result = result * base + value
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return result
|
|
56
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function bytesToHex (uint8aBytes) {
|
|
2
|
+
return Array.from(uint8aBytes).map(b => b.toString(16).padStart(2, '0')).join('')
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function hexToBytes (hexString) {
|
|
6
|
+
const arr = new Uint8Array(hexString.length / 2) // create result array
|
|
7
|
+
for (let i = 0; i < arr.length; i++) {
|
|
8
|
+
const j = i * 2
|
|
9
|
+
const h = hexString.slice(j, j + 2)
|
|
10
|
+
const b = Number.parseInt(h, 16) // byte, created from string part
|
|
11
|
+
if (Number.isNaN(b) || b < 0) throw new Error('invalid hex')
|
|
12
|
+
arr[i] = b
|
|
13
|
+
}
|
|
14
|
+
return arr
|
|
15
|
+
}
|
package/src/index.js
CHANGED
|
@@ -4,6 +4,7 @@ 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 { isNostrAppIdSafe, deriveNostrAppId } from '#helpers/app.js'
|
|
7
8
|
|
|
8
9
|
export default async function (...args) {
|
|
9
10
|
try {
|
|
@@ -12,6 +13,7 @@ export default async function (...args) {
|
|
|
12
13
|
await nostrRelays.disconnectAll()
|
|
13
14
|
}
|
|
14
15
|
}
|
|
16
|
+
|
|
15
17
|
export async function toApp (fileList, nostrSigner, { log = () => {}, appId, channel = 'main' } = {}) {
|
|
16
18
|
if (!nostrSigner && typeof window !== 'undefined') nostrSigner = window.nostr
|
|
17
19
|
if (!nostrSigner) throw new Error('No Nostr signer found')
|
|
@@ -19,8 +21,12 @@ export async function toApp (fileList, nostrSigner, { log = () => {}, appId, cha
|
|
|
19
21
|
nostrSigner.getRelays = NostrSigner.prototype.getRelays
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
appId
|
|
23
|
-
|
|
24
|
+
if (typeof appId === 'string') {
|
|
25
|
+
if (!isNostrAppIdSafe(appId)) throw new Error('appId should be [A-Za-z0-9] with length ranging from 1 to 19')
|
|
26
|
+
} else {
|
|
27
|
+
appId = fileList[0].webkitRelativePath.split('/')[0].trim()
|
|
28
|
+
if (!isNostrAppIdSafe(appId)) appId = deriveNostrAppId(appId || Math.random().toString(36))
|
|
29
|
+
}
|
|
24
30
|
let nmmr
|
|
25
31
|
const fileMetadata = []
|
|
26
32
|
|
|
@@ -5,6 +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 { bytesToHex, hexToBytes } from '#helpers/byte.js'
|
|
8
9
|
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
|
9
10
|
|
|
10
11
|
const dotenvPath = process.env.DOTENV_CONFIG_PATH ?? `${__dirname}/../../.env`
|
|
@@ -132,22 +133,6 @@ export default class NostrSigner {
|
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
function bytesToHex (uint8aBytes) {
|
|
136
|
-
return Array.from(uint8aBytes).map(b => b.toString(16).padStart(2, '0')).join('')
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function hexToBytes (hexString) {
|
|
140
|
-
const arr = new Uint8Array(hexString.length / 2) // create result array
|
|
141
|
-
for (let i = 0; i < arr.length; i++) {
|
|
142
|
-
const j = i * 2
|
|
143
|
-
const h = hexString.slice(j, j + 2)
|
|
144
|
-
const b = Number.parseInt(h, 16) // byte, created from string part
|
|
145
|
-
if (Number.isNaN(b) || b < 0) throw new Error('invalid hex')
|
|
146
|
-
arr[i] = b
|
|
147
|
-
}
|
|
148
|
-
return arr
|
|
149
|
-
}
|
|
150
|
-
|
|
151
136
|
function generateSecretKey () {
|
|
152
137
|
const randomBytes = crypto.getRandomValues(new Uint8Array(40))
|
|
153
138
|
const B256 = 2n ** 256n // secp256k1 is short weierstrass curve
|