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 CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "git+https://github.com/44billion/nappup.git"
7
7
  },
8
8
  "license": "GPL-3.0-or-later",
9
- "version": "1.0.5",
9
+ "version": "1.0.6",
10
10
  "description": "Nostr App Uploader",
11
11
  "type": "module",
12
12
  "scripts": {
@@ -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 ||= fileList[0].webkitRelativePath.split('/')[0]
23
- .trim().replace(/[\s-]/g, '').toLowerCase().slice(0, 32)
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