OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-identity commits / 97d1c8d

Unify ANProto protocol + add registry features + test suite
ANProto rewritten as superset of Wiredove content-signing and pear-git/hypernote challenge-response auth. Same ed25519 keypair works for both modes using base64 encoding throughout. - ANProto: sign/verify (detached, HTTP auth) + signContent/open (combined, Wiredove-compatible) + hash (SHA-256) + deterministicStringify + createMessage/verifyMessage - IdentityRegistry: add autoRegister(), username support (validate, set, check availability), updateUserProfile(), isRevoked() with view index, indexed getUserByUsername() - IdentityManager: base64 key storage, getPublicKeyBase64(), getKeypairString(), destroy() with sodium_memzero - Fix DeviceLinker callback binding (arrow functions) - Fix linkIdentityToUser race (direct-write before Autobase) - Fix revocation in apply (use view index, not identity prefix) - Add SET_USERNAME Autobase op type + apply handler - Add theme to UPDATE_USER apply handler - 46 tests, 119 assertions (brittle) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
97d1c8d60c513807d0d590f745b9a8a1ea90c8b9
by plankatron <fleeky@gmail.com> · 2026-03-24T22:44:48.000Z

Changed files (13) +2958 -80

.gitignore +1 -0
……@@ -0,0 +1 @@
1node_modules/
lib/anproto.js +250 -32
……@@ -1,66 +1,284 @@
1import crypto from 'hypercore-crypto'
2import b4a from 'b4a'
3
41/**
5 * ANProto — Ed25519 challenge-response authentication protocol
2 * ANProto — Unified Ed25519 identity protocol
3 *
4 * Superset of Wiredove content-signing and pear-git/hypernote challenge-response.
5 * Same keypair works for both modes.
6 *
7 * Challenge-response (HTTP auth):
8 * sign(challenge, keypair) → detached 88-char base64 signature
9 * verify(challenge, signature, publicKey) → boolean
610 *
7 * Used for browser-based identity verification.
8 * Generates keypairs, signs challenges, verifies signatures.
11 * Content signing (social posts, Wiredove-compatible):
12 * hash(data) → SHA-256 base64 hash
13 * signContent(hash, keypair) → pubkey(44) + base64(crypto_sign(ts+hash))
14 * open(message) → { timestamp, hash, publicKey, verified }
915 */
16
17import crypto from 'hypercore-crypto'
18import sodium from 'sodium-native'
19import b4a from 'b4a'
20
1021export class ANProto {
22 // ---------------------------------------------------------------------------
23 // Keypair management (shared by both modes)
24 // ---------------------------------------------------------------------------
25
1126 /**
1227 * Generate a new Ed25519 keypair
13 * @returns {{ publicKey: Buffer, secretKey: Buffer }}
28 * @returns {{ publicKey: Buffer, secretKey: Buffer, keypair: string }}
1429 */
1530 static gen () {
16 return crypto.keyPair()
31 const publicKey = b4a.allocUnsafe(sodium.crypto_sign_PUBLICKEYBYTES)
32 const secretKey = b4a.allocUnsafe(sodium.crypto_sign_SECRETKEYBYTES)
33 sodium.crypto_sign_keypair(publicKey, secretKey)
34
35 return {
36 publicKey,
37 secretKey,
38 keypair: b4a.toString(publicKey, 'base64') + b4a.toString(secretKey, 'base64')
39 }
1740 }
1841
1942 /**
20 * Load keypair from hex-encoded keys
21 * @param {{ publicKey: string, secretKey: string }} keys - Hex-encoded keys
43 * Load keypair from 132-char base64 string (44 pubkey + 88 secretkey)
44 * @param {string} keypairString
2245 * @returns {{ publicKey: Buffer, secretKey: Buffer }}
2346 */
24 static loadKeypair (keys) {
47 static loadKeypair (keypairString) {
48 if (typeof keypairString !== 'string' || keypairString.length !== 132) {
49 throw new Error('Invalid keypair length (expected 132 chars: 44 pubkey + 88 secretkey)')
50 }
51
2552 return {
26 publicKey: b4a.from(keys.publicKey, 'hex'),
27 secretKey: b4a.from(keys.secretKey, 'hex')
53 publicKey: b4a.from(keypairString.substring(0, 44), 'base64'),
54 secretKey: b4a.from(keypairString.substring(44, 132), 'base64')
2855 }
2956 }
3057
3158 /**
32 * Sign a challenge with secret key
33 * @param {Buffer|string} challenge - Challenge to sign
34 * @param {Buffer} secretKey - Secret key
35 * @returns {string} Base64-encoded signature
59 * Extract base64 public key from keypair string
60 * @param {string} keypairString - 132-char base64 keypair
61 * @returns {string} 44-char base64 public key
3662 */
37 static sign (challenge, secretKey) {
38 const msg = typeof challenge === 'string' ? b4a.from(challenge) : challenge
39 const sig = crypto.sign(msg, secretKey)
63 static getPublicKey (keypairString) {
64 if (typeof keypairString !== 'string' || keypairString.length !== 132) {
65 throw new Error('Invalid keypair length')
66 }
67 return keypairString.substring(0, 44)
68 }
69
70 // ---------------------------------------------------------------------------
71 // Challenge-response mode (HTTP auth — pear-git/hypernote pattern)
72 // Uses hypercore-crypto detached signatures
73 // ---------------------------------------------------------------------------
74
75 /**
76 * Sign a challenge (detached mode, for HTTP auth)
77 * @param {string|Buffer} challenge - Challenge to sign
78 * @param {{ publicKey: Buffer, secretKey: Buffer }} keypair
79 * @returns {string} 88-char base64 detached signature
80 */
81 static sign (challenge, keypair) {
82 const msg = typeof challenge === 'string' ? b4a.from(challenge, 'utf8') : challenge
83 const sig = crypto.sign(msg, keypair.secretKey)
4084 return b4a.toString(sig, 'base64')
4185 }
4286
4387 /**
44 * Verify a signature against a public key
45 * @param {Buffer|string} challenge - Original challenge
46 * @param {string} signature - Base64-encoded signature
47 * @param {Buffer|string} publicKey - Public key (Buffer or base64)
88 * Verify a detached challenge-response signature
89 * @param {string|Buffer} challenge - Original challenge
90 * @param {string} signature - 88-char base64 signature
91 * @param {string|Buffer} publicKey - Base64 or Buffer public key
4892 * @returns {boolean}
4993 */
5094 static verify (challenge, signature, publicKey) {
51 const msg = typeof challenge === 'string' ? b4a.from(challenge) : challenge
52 const sig = b4a.from(signature, 'base64')
53 const pk = typeof publicKey === 'string' ? b4a.from(publicKey, 'base64') : publicKey
54 return crypto.verify(msg, sig, pk)
95 try {
96 const msg = typeof challenge === 'string' ? b4a.from(challenge, 'utf8') : challenge
97 const sig = b4a.from(signature, 'base64')
98 const pk = typeof publicKey === 'string' ? b4a.from(publicKey, 'base64') : publicKey
99
100 if (pk.length !== 32) return false
101 if (sig.length !== 64) return false
102
103 return crypto.verify(msg, sig, pk)
104 } catch {
105 return false
106 }
107 }
108
109 // ---------------------------------------------------------------------------
110 // Content signing mode (social posts — Wiredove-compatible)
111 // Uses sodium-native combined signatures + SHA-256
112 // ---------------------------------------------------------------------------
113
114 /**
115 * SHA-256 hash of data
116 * @param {string|Buffer} data
117 * @returns {string} 44-char base64 hash
118 */
119 static hash (data) {
120 const buf = typeof data === 'string' ? b4a.from(data, 'utf8') : data
121 const out = b4a.allocUnsafe(sodium.crypto_hash_sha256_BYTES)
122 sodium.crypto_hash_sha256(out, buf)
123 return b4a.toString(out, 'base64')
124 }
125
126 /**
127 * Sign a content hash with embedded timestamp (Wiredove wire format)
128 *
129 * Output: pubkey(44 chars base64) + base64(crypto_sign(timestamp + hash))
130 * This is the combined-mode signature where the signed data is embedded.
131 *
132 * @param {string} contentHash - Base64 SHA-256 hash from hash()
133 * @param {{ publicKey: Buffer, secretKey: Buffer }} keypair
134 * @returns {string} ANProto content signature
135 */
136 static signContent (contentHash, keypair) {
137 const timestamp = Date.now().toString()
138 const message = timestamp + contentHash
139 const messageBuffer = b4a.from(message, 'utf8')
140
141 const signedMessage = b4a.allocUnsafe(sodium.crypto_sign_BYTES + messageBuffer.length)
142 sodium.crypto_sign(signedMessage, messageBuffer, keypair.secretKey)
143
144 const publicKeyB64 = b4a.toString(keypair.publicKey, 'base64')
145 const signedB64 = b4a.toString(signedMessage, 'base64')
146
147 return publicKeyB64 + signedB64
148 }
149
150 /**
151 * Open and verify a content signature (Wiredove wire format)
152 *
153 * Extracts the public key, verifies the combined-mode signature,
154 * and returns the embedded timestamp + content hash.
155 *
156 * @param {string} message - ANProto content signature from signContent()
157 * @returns {{ timestamp: number, hash: string, publicKey: string, verified: boolean }}
158 */
159 static open (message) {
160 if (typeof message !== 'string' || message.length < 44) {
161 throw new Error('Invalid ANProto message (too short)')
162 }
163
164 const publicKeyB64 = message.substring(0, 44)
165 const signedB64 = message.substring(44)
166
167 const publicKey = b4a.from(publicKeyB64, 'base64')
168 const signedMessage = b4a.from(signedB64, 'base64')
169
170 if (signedMessage.length <= sodium.crypto_sign_BYTES) {
171 throw new Error('Invalid ANProto message (signed data too short)')
172 }
173
174 const originalMessage = b4a.allocUnsafe(signedMessage.length - sodium.crypto_sign_BYTES)
175 const opened = sodium.crypto_sign_open(originalMessage, signedMessage, publicKey)
176
177 if (!opened) {
178 throw new Error('Invalid signature')
179 }
180
181 const messageStr = b4a.toString(originalMessage, 'utf8')
182 const timestampMatch = messageStr.match(/^\d+/)
183
184 if (!timestampMatch) {
185 throw new Error('Invalid message format (no timestamp)')
186 }
187
188 const timestamp = parseInt(timestampMatch[0], 10)
189 const hash = messageStr.substring(timestampMatch[0].length)
190
191 return {
192 timestamp,
193 hash,
194 publicKey: publicKeyB64,
195 verified: true
196 }
55197 }
56198
199 // ---------------------------------------------------------------------------
200 // High-level helpers
201 // ---------------------------------------------------------------------------
202
57203 /**
58 * Get hex-encoded public key
59 * @param {Buffer} publicKey - Public key buffer
204 * Deterministic JSON stringify (sorted keys, recursive)
205 * Ensures same object always produces same hash.
206 *
207 * @param {*} obj
60208 * @returns {string}
61209 */
62 static getPublicKey (publicKey) {
63 return b4a.toString(publicKey, 'hex')
210 static deterministicStringify (obj) {
211 if (obj === null) return 'null'
212 if (obj === undefined) return 'undefined'
213 if (typeof obj !== 'object') return JSON.stringify(obj)
214 if (Array.isArray(obj)) {
215 return '[' + obj.map(item => ANProto.deterministicStringify(item)).join(',') + ']'
216 }
217
218 const keys = Object.keys(obj).sort()
219 const pairs = keys.map(key =>
220 JSON.stringify(key) + ':' + ANProto.deterministicStringify(obj[key])
221 )
222 return '{' + pairs.join(',') + '}'
223 }
224
225 /**
226 * Create and sign a message (high-level content signing)
227 *
228 * Deterministically stringifies content, hashes it, signs the hash.
229 * The returned object includes the ANProto signature and can be verified
230 * with verifyMessage().
231 *
232 * @param {Object|string} content - Message content
233 * @param {{ publicKey: Buffer, secretKey: Buffer }} keypair
234 * @returns {{ anproto: string, content: *, hash: string }}
235 */
236 static createMessage (content, keypair) {
237 const contentStr = typeof content === 'object'
238 ? ANProto.deterministicStringify(content)
239 : String(content)
240 const contentHash = ANProto.hash(contentStr)
241 const anproto = ANProto.signContent(contentHash, keypair)
242
243 return {
244 anproto,
245 content,
246 hash: contentHash
247 }
248 }
249
250 /**
251 * Verify a signed message (high-level content verification)
252 *
253 * Opens the ANProto signature, recomputes the content hash,
254 * and verifies they match.
255 *
256 * @param {{ anproto: string, content: * }} message
257 * @returns {{ author: string, verified: boolean, timestamp: number, content: *, hash: string }}
258 */
259 static verifyMessage (message) {
260 if (!message || !message.anproto) {
261 throw new Error('Missing ANProto signature')
262 }
263
264 const verification = ANProto.open(message.anproto)
265
266 const contentStr = typeof message.content === 'object'
267 ? ANProto.deterministicStringify(message.content)
268 : String(message.content)
269 const expectedHash = ANProto.hash(contentStr)
270
271 if (verification.hash !== expectedHash) {
272 throw new Error('Content hash mismatch')
273 }
274
275 return {
276 content: message.content,
277 hash: verification.hash,
278 author: verification.publicKey,
279 timestamp: verification.timestamp,
280 verified: true
281 }
64282 }
65283}
66284
lib/auth/identity-apply.js +28 -17
……@@ -63,6 +63,7 @@ export function createApplyFunction (opts = {}) {
6363 if (op.name !== undefined) user.name = op.name
6464 if (op.email !== undefined) user.email = op.email
6565 if (op.profilePicture !== undefined) user.profilePicture = op.profilePicture
66 if (op.theme !== undefined) user.theme = op.theme
6667 if (op.globalPermissions !== undefined) user.globalPermissions = op.globalPermissions
6768 user.updatedAt = Date.now()
6869 await b.put(key, user)
……@@ -195,28 +196,38 @@ export function createApplyFunction (opts = {}) {
195196 }
196197
197198 case OpTypes.REVOKE_IDENTITY: {
198 const idKey = `${prefix}identity:${op.identityId}`
199 const existing = await b.get(idKey)
200 if (existing) {
201 const identity = existing.value
202 identity.revoked = true
203 identity.revokedAt = op.revokedAt || Date.now()
204 identity.revokedReason = op.reason || null
205 await b.put(idKey, identity)
206 }
199 // Write revocation record to view (consensus-visible)
200 // Direct-write db is updated separately in registry method
201 await b.put(`${prefix}revoked:${op.identityId}`, {
202 identityId: op.identityId,
203 revokedAt: op.revokedAt || Date.now(),
204 reason: op.reason || null
205 })
207206 break
208207 }
209208
210209 case OpTypes.RESTORE_IDENTITY: {
211 const idKey = `${prefix}identity:${op.identityId}`
212 const existing = await b.get(idKey)
213 if (existing) {
214 const identity = existing.value
215 delete identity.revoked
216 delete identity.revokedAt
217 delete identity.revokedReason
218 await b.put(idKey, identity)
210 await b.del(`${prefix}revoked:${op.identityId}`)
211 break
212 }
213
214 case OpTypes.SET_USERNAME: {
215 const key = `${prefix}user:${op.userId}`
216 const existing = await b.get(key)
217 if (!existing) continue
218
219 const user = existing.value
220
221 // Remove old username index
222 if (user.username) {
223 await b.del(`${prefix}username:${user.username}`)
219224 }
225
226 // Set new username
227 user.username = op.username
228 user.updatedAt = Date.now()
229 await b.put(key, user)
230 await b.put(`${prefix}username:${op.username}`, { userId: op.userId })
220231 break
221232 }
222233 }
lib/auth/identity-operations.js +7 -1
……@@ -13,7 +13,8 @@ export const OpTypes = {
1313 UPGRADE_PERMISSIONS: 'upgrade-permissions',
1414 MERGE_ACCOUNTS: 'merge-accounts',
1515 REVOKE_IDENTITY: 'revoke-identity',
16 RESTORE_IDENTITY: 'restore-identity'
16 RESTORE_IDENTITY: 'restore-identity',
17 SET_USERNAME: 'set-username'
1718}
1819
1920/**
……@@ -83,6 +84,11 @@ export function validateOp (op) {
8384 if (!op.identityId) return { valid: false, error: 'Missing identityId' }
8485 return { valid: true }
8586
87 case OpTypes.SET_USERNAME:
88 if (!op.userId) return { valid: false, error: 'Missing userId' }
89 if (!op.username) return { valid: false, error: 'Missing username' }
90 return { valid: true }
91
8692 default:
8793 return { valid: false, error: `Unknown op type: ${op.type}` }
8894 }
lib/auth/identity-registry.js +209 -16
……@@ -5,6 +5,13 @@ import b4a from 'b4a'
55import { createApplyFunction } from './identity-apply.js'
66import { encodeOp, OpTypes } from './identity-operations.js'
77
8const RESERVED_USERNAMES = [
9 'api', 'admin', 'web', 'sync', 'static', 'assets', 'auth',
10 'health', 'status', 'login', 'logout', 'register', 'settings'
11]
12
13const USERNAME_REGEX = /^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$/
14
815/**
916 * IdentityRegistry — Autobase-backed user and identity management
1017 *
……@@ -103,17 +110,13 @@ export class IdentityRegistry {
103110 }
104111
105112 /**
106 * Get user by username
113 * Get user by username (indexed lookup)
107114 */
108115 async getUserByUsername (username) {
109116 if (!username) return null
110 for await (const entry of this._view.createReadStream({
111 gte: `${this._prefix}user:`,
112 lt: `${this._prefix}user:\xff`
113 })) {
114 if (entry.value.username === username) return entry.value
115 }
116 return null
117 const entry = await this._view.get(`${this._prefix}username:${username}`)
118 if (!entry) return null
119 return this.getUser(entry.value.userId)
117120 }
118121
119122 /**
……@@ -188,21 +191,23 @@ export class IdentityRegistry {
188191 * Link identity to user via Autobase
189192 */
190193 async linkIdentityToUser (identityId, userId) {
191 await this._base.append(encodeOp({
192 type: OpTypes.LINK_IDENTITY,
193 identityId,
194 userId
195 }))
196 await this._base.update()
197
198 // Also update direct-write identity record
194 // Update direct-write db first (source of truth for identity records)
199195 if (this.db) {
200196 const identity = await this.getIdentity(identityId)
201197 if (identity) {
202198 identity.userId = userId
199 identity.updatedAt = Date.now()
203200 await this.db.put(`identity:${identityId}`, identity)
204201 }
205202 }
203
204 // Then append to Autobase for multi-writer consensus
205 await this._base.append(encodeOp({
206 type: OpTypes.LINK_IDENTITY,
207 identityId,
208 userId
209 }))
210 await this._base.update()
206211 }
207212
208213 /**
……@@ -351,6 +356,194 @@ export class IdentityRegistry {
351356 return revoked
352357 }
353358
359 // ==================== AUTO-REGISTRATION ====================
360
361 /**
362 * Auto-register: atomically create user + identity for first-time auth.
363 * First user gets admin, subsequent get read-only.
364 *
365 * @param {Object} identityData - { identityId, method, credentials, name? }
366 * @returns {{ user: Object, identity: Object }}
367 */
368 async autoRegister (identityData) {
369 const hasAdmin = await this._hasAnyAdmin()
370
371 const permissions = hasAdmin ? ['read'] : ['read', 'write', 'admin']
372 const resources = hasAdmin
373 ? [{ name: '*', permissions: ['read'] }]
374 : [{ name: '*', permissions: ['read', 'write', 'admin'] }]
375
376 const user = await this.createUser({
377 name: identityData.name || 'Anonymous',
378 globalPermissions: permissions,
379 resources
380 })
381
382 // Register identity in direct-write store
383 const identity = {
384 identityId: identityData.identityId,
385 method: identityData.method,
386 publicKey: identityData.credentials ? identityData.credentials.publicKey : identityData.identityId,
387 userId: user.userId,
388 createdAt: Date.now()
389 }
390 await this.registerIdentity(identity)
391
392 // Link identity to user
393 await this.linkIdentityToUser(identity.identityId, user.userId)
394
395 return { user, identity }
396 }
397
398 /**
399 * Check if any users exist
400 */
401 async _hasAnyUsers () {
402 for await (const _ of this._view.createReadStream({ // eslint-disable-line no-unused-vars
403 gte: `${this._prefix}user:`,
404 lt: `${this._prefix}user:\xff`,
405 limit: 1
406 })) {
407 return true
408 }
409 return false
410 }
411
412 // ==================== USERNAME MANAGEMENT ====================
413
414 /**
415 * Validate username format
416 * @param {string} username
417 * @returns {{ valid: boolean, reason?: string }}
418 */
419 validateUsername (username) {
420 if (!username || typeof username !== 'string') {
421 return { valid: false, reason: 'Username is required' }
422 }
423
424 const lower = username.toLowerCase()
425 if (lower !== username) {
426 return { valid: false, reason: 'Username must be lowercase' }
427 }
428
429 if (username.length < 3 || username.length > 32) {
430 return { valid: false, reason: 'Username must be 3-32 characters' }
431 }
432
433 if (!USERNAME_REGEX.test(username)) {
434 return { valid: false, reason: 'Username must be lowercase alphanumeric with dashes, no leading/trailing dash' }
435 }
436
437 if (username.includes('--')) {
438 return { valid: false, reason: 'Username must not contain consecutive dashes' }
439 }
440
441 if (RESERVED_USERNAMES.includes(username)) {
442 return { valid: false, reason: 'Username is reserved' }
443 }
444
445 return { valid: true }
446 }
447
448 /**
449 * Set username for a user (unique, validated)
450 * @param {string} userId
451 * @param {string} username
452 * @returns {Promise<Object>} Updated user from view
453 */
454 async setUsername (userId, username) {
455 const validation = this.validateUsername(username)
456 if (!validation.valid) {
457 const err = new Error(validation.reason)
458 err.code = 'VALIDATION_ERROR'
459 err.status = 400
460 throw err
461 }
462
463 // Check uniqueness in view
464 const existing = await this._view.get(`${this._prefix}username:${username}`)
465 if (existing && existing.value.userId !== userId) {
466 const err = new Error('Username already taken')
467 err.code = 'CONFLICT'
468 err.status = 409
469 throw err
470 }
471
472 // Emit Autobase op for multi-writer consensus
473 await this._base.append(encodeOp({
474 type: OpTypes.SET_USERNAME,
475 userId,
476 username
477 }))
478 await this._base.update()
479
480 // Also update direct-write db if present
481 if (this.db) {
482 const user = await this.getUser(userId)
483 if (user) {
484 if (user.username) {
485 await this.db.del(`username:${user.username}`)
486 }
487 await this.db.put(`username:${username}`, { userId })
488 }
489 }
490
491 return this.getUser(userId)
492 }
493
494 /**
495 * Check if username is available
496 * @param {string} username
497 * @returns {Promise<boolean>}
498 */
499 async isUsernameAvailable (username) {
500 const validation = this.validateUsername(username)
501 if (!validation.valid) return false
502
503 const entry = await this._view.get(`${this._prefix}username:${username}`)
504 return !entry
505 }
506
507 // ==================== PROFILE UPDATES ====================
508
509 /**
510 * Update user profile (name, picture, theme)
511 * @param {string} userId
512 * @param {Object} updates - { name?, profilePicture?, theme? }
513 * @returns {Promise<Object>} Updated user
514 */
515 async updateUserProfile (userId, updates) {
516 const filtered = {}
517 if (updates.name !== undefined) filtered.name = updates.name
518 if (updates.profilePicture !== undefined) filtered.profilePicture = updates.profilePicture
519 if (updates.theme !== undefined) filtered.theme = updates.theme
520
521 await this.updateUser(userId, filtered)
522 return this.getUser(userId)
523 }
524
525 // ==================== REVOCATION (ENHANCED) ====================
526
527 /**
528 * Check if identity is revoked (fast lookup via view index)
529 * @param {string} identityId
530 * @returns {Promise<boolean>}
531 */
532 async isRevoked (identityId) {
533 // Check Autobase view revocation index
534 const revoked = await this._view.get(`${this._prefix}revoked:${identityId}`)
535 if (revoked) return true
536
537 // Fallback: check direct-write identity record
538 if (this.db) {
539 const identity = await this.getIdentity(identityId)
540 if (identity && identity.revoked) return true
541 }
542 return false
543 }
544
545 // ==================== FEDERATION & LIFECYCLE ====================
546
354547 /**
355548 * Add Autobase writer key
356549 */
lib/device-linker.js +11 -8
……@@ -21,18 +21,18 @@ export class DeviceLinker {
2121 */
2222 async createInvite () {
2323 return new Promise((resolve, reject) => {
24 const mnemonic = this._identityManager ? this._identityManager.getMnemonic() : null
24 const identityManager = this._identityManager
25 const mnemonic = identityManager ? identityManager.getMnemonic() : null
2526 if (!mnemonic) {
2627 reject(new Error('No mnemonic available to share'))
2728 return
2829 }
2930
3031 const member = BlindPairing.member(this._swarm, {
31 onadd (candidate) {
32 // Send mnemonic to paired device
32 onadd: (candidate) => {
3333 candidate.open(b4a.from(JSON.stringify({
3434 mnemonic,
35 instanceKey: this._identityManager ? this._identityManager.getPublicKeyHex() : null
35 instanceKey: identityManager ? identityManager.getPublicKeyHex() : null
3636 })))
3737 }
3838 })
……@@ -53,17 +53,20 @@ export class DeviceLinker {
5353 * @returns {Promise<Object>} { mnemonic, instanceKey }
5454 */
5555 async joinWithInvite (inviteHex) {
56 const identityManager = this._identityManager
57 const onLinked = this._onLinked
58
5659 return new Promise((resolve, reject) => {
5760 const invite = b4a.from(inviteHex, 'hex')
5861
5962 const candidate = BlindPairing.candidate(this._swarm, invite, {
60 onopen (data) {
63 onopen: (data) => {
6164 try {
6265 const payload = JSON.parse(b4a.toString(data))
63 if (this._identityManager && payload.mnemonic) {
64 this._identityManager.setMnemonic(payload.mnemonic).catch(() => {})
66 if (identityManager && payload.mnemonic) {
67 identityManager.setMnemonic(payload.mnemonic).catch(() => {})
6568 }
66 if (this._onLinked) this._onLinked(payload.mnemonic, payload)
69 if (onLinked) onLinked(payload.mnemonic, payload)
6770 resolve(payload)
6871 } catch (err) {
6972 reject(err)
lib/identity-manager.js +41 -5
……@@ -1,4 +1,5 @@
11import crypto from 'hypercore-crypto'
2import sodium from 'sodium-native'
23import b4a from 'b4a'
34
45/**
……@@ -25,16 +26,19 @@ export class IdentityManager {
2526 // Try to load existing keypair
2627 const existing = await this._store.get('instance-keypair')
2728 if (existing) {
29 const val = existing.value || existing
30 // Support both base64 (new) and hex (legacy) storage formats
31 const encoding = (val.publicKey && val.publicKey.length === 44) ? 'base64' : 'hex'
2832 this._keypair = {
29 publicKey: b4a.from(existing.value.publicKey, 'hex'),
30 secretKey: b4a.from(existing.value.secretKey, 'hex')
33 publicKey: b4a.from(val.publicKey, encoding),
34 secretKey: b4a.from(val.secretKey, encoding)
3135 }
3236 } else {
33 // Generate new persistent keypair
37 // Generate new persistent keypair, store as base64
3438 this._keypair = crypto.keyPair()
3539 await this._store.put('instance-keypair', {
36 publicKey: b4a.toString(this._keypair.publicKey, 'hex'),
37 secretKey: b4a.toString(this._keypair.secretKey, 'hex')
40 publicKey: b4a.toString(this._keypair.publicKey, 'base64'),
41 secretKey: b4a.toString(this._keypair.secretKey, 'base64')
3842 })
3943 }
4044
……@@ -135,6 +139,23 @@ export class IdentityManager {
135139 return crypto.verify(data, signature, this._keypair.publicKey)
136140 }
137141
142 /**
143 * Get instance public key as 44-char base64
144 */
145 getPublicKeyBase64 () {
146 if (!this._keypair) return null
147 return b4a.toString(this._keypair.publicKey, 'base64')
148 }
149
150 /**
151 * Get instance keypair as 132-char base64 string (ANProto format)
152 */
153 getKeypairString () {
154 if (!this._keypair) return null
155 return b4a.toString(this._keypair.publicKey, 'base64') +
156 b4a.toString(this._keypair.secretKey, 'base64')
157 }
158
138159 /**
139160 * Get identity public key (from mnemonic, distinct from instance key)
140161 */
……@@ -150,6 +171,21 @@ export class IdentityManager {
150171 const pk = this.getIdentityPublicKey()
151172 return pk ? b4a.toString(pk, 'hex') : null
152173 }
174
175 /**
176 * Zero sensitive key material from memory
177 */
178 destroy () {
179 if (this._keypair && this._keypair.secretKey) {
180 sodium.sodium_memzero(this._keypair.secretKey)
181 }
182 if (this._identityKey && typeof this._identityKey.clear === 'function') {
183 this._identityKey.clear()
184 }
185 this._keypair = null
186 this._identityKey = null
187 this._mnemonic = null
188 }
153189}
154190
155191export default IdentityManager
package-lock.json +1774 -0
……@@ -0,0 +1,1774 @@
1{
2 "name": "hyper-identity",
3 "version": "1.0.0",
4 "lockfileVersion": 3,
5 "requires": true,
6 "packages": {
7 "": {
8 "name": "hyper-identity",
9 "version": "1.0.0",
10 "license": "MIT",
11 "dependencies": {
12 "autobase": "^7.25.1",
13 "b4a": "^1.6.4",
14 "bip39-mnemonic": "^2.5.0",
15 "blind-pairing": "^2.3.1",
16 "corestore": "^7.8.0",
17 "hyperbee": "^2.27.3",
18 "hypercore": "^11.24.0",
19 "hypercore-crypto": "^3.0.0",
20 "hyperswarm": "^4.16.0",
21 "keet-identity-key": "github:holepunchto/keet-identity-key",
22 "sodium-native": "^4.3.1"
23 },
24 "devDependencies": {
25 "brittle": "^3.19.1",
26 "random-access-memory": "^6.2.1"
27 }
28 },
29 "node_modules/@hyperswarm/secret-stream": {
30 "version": "6.9.1",
31 "resolved": "https://registry.npmjs.org/@hyperswarm/secret-stream/-/secret-stream-6.9.1.tgz",
32 "integrity": "sha512-xb0S5y3YJwBakD77JOGBHlBxdp63mHClZoXBYoLv+9wH8e054ESKlmQptWqjJK5dv5VMUIVYOJB4MaOpB0JdGw==",
33 "license": "Apache-2.0",
34 "dependencies": {
35 "b4a": "^1.1.0",
36 "hypercore-crypto": "^3.3.1",
37 "noise-curve-ed": "^2.0.1",
38 "noise-handshake": "^4.0.0",
39 "sodium-secretstream": "^1.1.0",
40 "sodium-universal": "^5.0.0",
41 "streamx": "^2.14.0",
42 "timeout-refresh": "^2.0.0",
43 "unslab": "^1.3.0"
44 }
45 },
46 "node_modules/@jridgewell/resolve-uri": {
47 "version": "3.1.2",
48 "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
49 "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
50 "dev": true,
51 "license": "MIT",
52 "engines": {
53 "node": ">=6.0.0"
54 }
55 },
56 "node_modules/@jridgewell/sourcemap-codec": {
57 "version": "1.5.5",
58 "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
59 "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
60 "dev": true,
61 "license": "MIT"
62 },
63 "node_modules/@jridgewell/trace-mapping": {
64 "version": "0.3.31",
65 "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
66 "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
67 "dev": true,
68 "license": "MIT",
69 "dependencies": {
70 "@jridgewell/resolve-uri": "^3.1.0",
71 "@jridgewell/sourcemap-codec": "^1.4.14"
72 }
73 },
74 "node_modules/@types/istanbul-lib-coverage": {
75 "version": "2.0.6",
76 "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
77 "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
78 "dev": true,
79 "license": "MIT"
80 },
81 "node_modules/adaptive-timeout": {
82 "version": "1.0.1",
83 "resolved": "https://registry.npmjs.org/adaptive-timeout/-/adaptive-timeout-1.0.1.tgz",
84 "integrity": "sha512-+seGiBtHqbnyZ0Quq/ZGxxwP66153WBABawa07/QeyuPFzbduPBjiGQAyCiriiLDzt3dkbMBskSlfqtRwblK8Q==",
85 "license": "Apache-2.0",
86 "dependencies": {
87 "xache": "^1.2.1"
88 }
89 },
90 "node_modules/autobase": {
91 "version": "7.27.3",
92 "resolved": "https://registry.npmjs.org/autobase/-/autobase-7.27.3.tgz",
93 "integrity": "sha512-eH0UUYYO2kvy9Vug0KLj/mjjSGEslA6YL7axBlPsArlmadBDJmkYj3olpbWVqnsA+VtTndiHGQZyC/004x/aVw==",
94 "license": "Apache-2.0",
95 "dependencies": {
96 "b4a": "^1.6.1",
97 "bare-events": "^2.2.0",
98 "compact-encoding": "^2.16.0",
99 "core-coupler": "^2.0.0",
100 "debounceify": "^1.0.0",
101 "encryption-encoding": "^1.0.3",
102 "hyperbee": "^2.22.0",
103 "hypercore": "^11.27.12",
104 "hypercore-crypto": "^3.4.0",
105 "hypercore-id-encoding": "^1.2.0",
106 "hyperschema": "^1.12.1",
107 "index-encoder": "^3.3.2",
108 "nanoassert": "^2.0.0",
109 "protomux-wakeup": "^2.0.0",
110 "ready-resource": "^1.0.0",
111 "resolve-reject-promise": "^1.1.0",
112 "safety-catch": "^1.0.2",
113 "scope-lock": "^1.2.4",
114 "signal-promise": "^1.0.3",
115 "sodium-universal": "^5.0.1",
116 "sub-encoder": "^2.1.1",
117 "tiny-buffer-map": "^1.1.1"
118 }
119 },
120 "node_modules/b4a": {
121 "version": "1.8.0",
122 "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz",
123 "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==",
124 "license": "Apache-2.0",
125 "peerDependencies": {
126 "react-native-b4a": "*"
127 },
128 "peerDependenciesMeta": {
129 "react-native-b4a": {
130 "optional": true
131 }
132 }
133 },
134 "node_modules/bare-abort": {
135 "version": "2.0.13",
136 "resolved": "https://registry.npmjs.org/bare-abort/-/bare-abort-2.0.13.tgz",
137 "integrity": "sha512-zdc8l88eB11Jsz5rDd6sCAgv2kUFXgdrZWoMlgU6JMkfAi1/uuGFC3IEHswKbIRQTk5H3T5CMuechsXYxiaHlQ==",
138 "dev": true,
139 "license": "Apache-2.0"
140 },
141 "node_modules/bare-addon-resolve": {
142 "version": "1.10.0",
143 "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz",
144 "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==",
145 "license": "Apache-2.0",
146 "dependencies": {
147 "bare-module-resolve": "^1.10.0",
148 "bare-semver": "^1.0.0"
149 },
150 "peerDependencies": {
151 "bare-url": "*"
152 },
153 "peerDependenciesMeta": {
154 "bare-url": {
155 "optional": true
156 }
157 }
158 },
159 "node_modules/bare-ansi-escapes": {
160 "version": "2.2.3",
161 "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz",
162 "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==",
163 "license": "Apache-2.0",
164 "dependencies": {
165 "bare-stream": "^2.6.5"
166 },
167 "peerDependencies": {
168 "bare-buffer": "*"
169 },
170 "peerDependenciesMeta": {
171 "bare-buffer": {
172 "optional": true
173 }
174 }
175 },
176 "node_modules/bare-assert": {
177 "version": "1.2.0",
178 "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz",
179 "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==",
180 "license": "Apache-2.0",
181 "dependencies": {
182 "bare-inspect": "^3.1.2"
183 }
184 },
185 "node_modules/bare-bundle": {
186 "version": "1.10.0",
187 "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz",
188 "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==",
189 "dev": true,
190 "license": "Apache-2.0",
191 "peerDependencies": {
192 "bare-buffer": "*",
193 "bare-url": "*"
194 },
195 "peerDependenciesMeta": {
196 "bare-buffer": {
197 "optional": true
198 },
199 "bare-url": {
200 "optional": true
201 }
202 }
203 },
204 "node_modules/bare-cov": {
205 "version": "1.2.1",
206 "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.1.tgz",
207 "integrity": "sha512-SHNqb/WG9udUEj8m5qGSehoUQ9o0Xzv8nrf4HIdmG+KNU/XdhPLhTP5VPrHnSAMAXhAzEB4Q+2IYZ0x9qPzN1g==",
208 "dev": true,
209 "license": "Apache-2.0",
210 "dependencies": {
211 "bare-fs": "^4.1.2",
212 "bare-inspector": "^4.0.1",
213 "bare-path": "^3.0.0",
214 "bare-process": "^4.2.1",
215 "bare-url": "^2.1.5",
216 "bare-v8-to-istanbul": "^1.0.2",
217 "picomatch": "^4.0.2",
218 "which-runtime": "^1.2.1"
219 }
220 },
221 "node_modules/bare-crypto": {
222 "version": "1.13.4",
223 "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.4.tgz",
224 "integrity": "sha512-JiCZ5l2YOG1y8J7yy1BCAKTCZrPnHLb7pDRIdurBTOn5oIwBQDIv8iH5Pl2V85vzjl1NZXRfNY4HZLsE942jJA==",
225 "dev": true,
226 "license": "Apache-2.0",
227 "dependencies": {
228 "bare-assert": "^1.2.0",
229 "bare-stream": "^2.6.3"
230 },
231 "peerDependencies": {
232 "bare-buffer": "*"
233 },
234 "peerDependenciesMeta": {
235 "bare-buffer": {
236 "optional": true
237 }
238 }
239 },
240 "node_modules/bare-debug-log": {
241 "version": "2.0.0",
242 "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz",
243 "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==",
244 "dev": true,
245 "license": "Apache-2.0",
246 "dependencies": {
247 "bare-os": "^3.0.1"
248 }
249 },
250 "node_modules/bare-dns": {
251 "version": "2.1.4",
252 "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz",
253 "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==",
254 "dev": true,
255 "license": "Apache-2.0",
256 "engines": {
257 "bare": ">=1.7.0"
258 }
259 },
260 "node_modules/bare-encoding": {
261 "version": "1.0.3",
262 "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz",
263 "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==",
264 "dev": true,
265 "license": "Apache-2.0",
266 "peerDependencies": {
267 "bare-buffer": "*"
268 },
269 "peerDependenciesMeta": {
270 "bare-buffer": {
271 "optional": true
272 }
273 }
274 },
275 "node_modules/bare-env": {
276 "version": "3.0.0",
277 "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz",
278 "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==",
279 "dev": true,
280 "license": "Apache-2.0",
281 "dependencies": {
282 "bare-os": "^3.0.1"
283 }
284 },
285 "node_modules/bare-events": {
286 "version": "2.8.2",
287 "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
288 "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
289 "license": "Apache-2.0",
290 "peerDependencies": {
291 "bare-abort-controller": "*"
292 },
293 "peerDependenciesMeta": {
294 "bare-abort-controller": {
295 "optional": true
296 }
297 }
298 },
299 "node_modules/bare-format": {
300 "version": "1.0.2",
301 "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.2.tgz",
302 "integrity": "sha512-GswdhnOnP9QtwRbrf4wLApw5widkaLMsLe2XOs35fQD2YfEN1ApoGka+cZ7PfvzxMgfYXmMhj/2OGlVn5/Dxgw==",
303 "dev": true,
304 "license": "Apache-2.0",
305 "dependencies": {
306 "bare-inspect": "^3.0.0"
307 }
308 },
309 "node_modules/bare-fs": {
310 "version": "4.5.6",
311 "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.6.tgz",
312 "integrity": "sha512-1QovqDrR80Pmt5HPAsMsXTCFcDYr+NSUKW6nd6WO5v0JBmnItc/irNRzm2KOQ5oZ69P37y+AMujNyNtG+1Rggw==",
313 "license": "Apache-2.0",
314 "dependencies": {
315 "bare-events": "^2.5.4",
316 "bare-path": "^3.0.0",
317 "bare-stream": "^2.6.4",
318 "bare-url": "^2.2.2",
319 "fast-fifo": "^1.3.2"
320 },
321 "engines": {
322 "bare": ">=1.16.0"
323 },
324 "peerDependencies": {
325 "bare-buffer": "*"
326 },
327 "peerDependenciesMeta": {
328 "bare-buffer": {
329 "optional": true
330 }
331 }
332 },
333 "node_modules/bare-hrtime": {
334 "version": "2.1.1",
335 "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz",
336 "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==",
337 "dev": true,
338 "license": "Apache-2.0"
339 },
340 "node_modules/bare-http-parser": {
341 "version": "1.1.3",
342 "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.1.3.tgz",
343 "integrity": "sha512-+dhVvQi6brHq14L/XHNRQ+TLuVE76VjRmMt61wVEtS+Od8xUslfMHWJN/ZjIIt3RtTG6vPuA+x9cOh7KrkBJsA==",
344 "dev": true,
345 "license": "Apache-2.0"
346 },
347 "node_modules/bare-http1": {
348 "version": "4.5.5",
349 "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.5.tgz",
350 "integrity": "sha512-ADITiRo0huP76JGMbv6Arsh9KehHqjEBoYcmjvAo67IY78+/9mV2MeKLLCkiJSFxA85T/m8cTaE44OwJQCcwdw==",
351 "dev": true,
352 "license": "Apache-2.0",
353 "dependencies": {
354 "bare-events": "^2.6.0",
355 "bare-http-parser": "^1.1.1",
356 "bare-stream": "^2.10.0",
357 "bare-tcp": "^2.2.0"
358 },
359 "peerDependencies": {
360 "bare-buffer": "*",
361 "bare-url": "*"
362 },
363 "peerDependenciesMeta": {
364 "bare-buffer": {
365 "optional": true
366 },
367 "bare-url": {
368 "optional": true
369 }
370 }
371 },
372 "node_modules/bare-https": {
373 "version": "2.1.3",
374 "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.3.tgz",
375 "integrity": "sha512-0TI/mJXQGYXmG7UUyWEG+KCJusayIAQLywUjFAskDoKuxfqVnGF+M/mTMrEV8J64DaIdU+5x761FvgmT7M68tA==",
376 "dev": true,
377 "license": "Apache-2.0",
378 "dependencies": {
379 "bare-http1": "^4.4.0",
380 "bare-tcp": "^2.2.0",
381 "bare-tls": "^2.0.0"
382 }
383 },
384 "node_modules/bare-inspect": {
385 "version": "3.1.4",
386 "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz",
387 "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==",
388 "license": "Apache-2.0",
389 "dependencies": {
390 "bare-ansi-escapes": "^2.1.0",
391 "bare-type": "^1.0.0"
392 },
393 "engines": {
394 "bare": ">=1.18.0"
395 }
396 },
397 "node_modules/bare-inspector": {
398 "version": "4.0.3",
399 "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-4.0.3.tgz",
400 "integrity": "sha512-yYk6+7tTVKJKHOja5+QBn989ETv8oVsmwoH0nCM+QYC6w5MC03GC4aQNK+lipAZSz1EaAj42WZ8Wewjpy0L9TA==",
401 "dev": true,
402 "license": "Apache-2.0",
403 "dependencies": {
404 "bare-events": "^2.1.0",
405 "bare-http1": "^4.0.0",
406 "bare-stream": "^2.0.0",
407 "bare-url": "^2.0.0",
408 "bare-ws": "^2.0.0"
409 },
410 "engines": {
411 "bare": ">=1.2.0"
412 },
413 "peerDependencies": {
414 "bare-tcp": "*"
415 },
416 "peerDependenciesMeta": {
417 "bare-tcp": {
418 "optional": true
419 }
420 }
421 },
422 "node_modules/bare-module": {
423 "version": "6.1.3",
424 "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz",
425 "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==",
426 "dev": true,
427 "license": "Apache-2.0",
428 "dependencies": {
429 "bare-bundle": "^1.3.0",
430 "bare-module-lexer": "^1.0.0",
431 "bare-module-resolve": "^1.8.0",
432 "bare-path": "^3.0.0",
433 "bare-url": "^2.0.1"
434 },
435 "engines": {
436 "bare": ">=1.23.0"
437 },
438 "peerDependencies": {
439 "bare-buffer": "*"
440 },
441 "peerDependenciesMeta": {
442 "bare-buffer": {
443 "optional": true
444 }
445 }
446 },
447 "node_modules/bare-module-lexer": {
448 "version": "1.4.7",
449 "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz",
450 "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==",
451 "dev": true,
452 "license": "Apache-2.0",
453 "dependencies": {
454 "require-addon": "^1.0.2"
455 },
456 "peerDependencies": {
457 "bare-buffer": "*"
458 },
459 "peerDependenciesMeta": {
460 "bare-buffer": {
461 "optional": true
462 }
463 }
464 },
465 "node_modules/bare-module-resolve": {
466 "version": "1.12.1",
467 "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz",
468 "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==",
469 "license": "Apache-2.0",
470 "dependencies": {
471 "bare-semver": "^1.0.0"
472 },
473 "peerDependencies": {
474 "bare-url": "*"
475 },
476 "peerDependenciesMeta": {
477 "bare-url": {
478 "optional": true
479 }
480 }
481 },
482 "node_modules/bare-net": {
483 "version": "2.3.1",
484 "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.3.1.tgz",
485 "integrity": "sha512-MypSqDKpDU2Xt7FIfazn5yGvRnV09gFcIPHGWstW0gxuzA4tucTcwJSZeos97C4F89vtU5oGwXDN/HrGN6Y4Jw==",
486 "dev": true,
487 "license": "Apache-2.0",
488 "dependencies": {
489 "bare-events": "^2.2.2",
490 "bare-pipe": "^4.0.0",
491 "bare-stream": "^2.0.0",
492 "bare-tcp": "^2.0.0"
493 }
494 },
495 "node_modules/bare-os": {
496 "version": "3.8.0",
497 "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.0.tgz",
498 "integrity": "sha512-Dc9/SlwfxkXIGYhvMQNUtKaXCaGkZYGcd1vuNUUADVqzu4/vQfvnMkYYOUnt2VwQ2AqKr/8qAVFRtwETljgeFg==",
499 "license": "Apache-2.0",
500 "engines": {
501 "bare": ">=1.14.0"
502 }
503 },
504 "node_modules/bare-path": {
505 "version": "3.0.0",
506 "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
507 "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
508 "license": "Apache-2.0",
509 "dependencies": {
510 "bare-os": "^3.0.1"
511 }
512 },
513 "node_modules/bare-pipe": {
514 "version": "4.1.5",
515 "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.5.tgz",
516 "integrity": "sha512-6OfxaG8JSkRh3Gc4hzHRsxNt+yu2PpN7lrv1V+T78GdknWQkVGwiEvu4m+1nbfk8cMVQ0TGxRvQ90XA4rhnTuw==",
517 "dev": true,
518 "license": "Apache-2.0",
519 "dependencies": {
520 "bare-events": "^2.0.0",
521 "bare-stream": "^2.0.0"
522 },
523 "engines": {
524 "bare": ">=1.16.0"
525 }
526 },
527 "node_modules/bare-process": {
528 "version": "4.3.0",
529 "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.3.0.tgz",
530 "integrity": "sha512-HwtAdKbqS98V+jeWAagUdzkrJR4vtuPeeWlsoeD1YKTqM1hKUWM2eU+l2diUYeTqFfxY9b6J9SsBLuBukH0tMQ==",
531 "dev": true,
532 "license": "Apache-2.0",
533 "dependencies": {
534 "bare-abort": "^2.0.13",
535 "bare-env": "^3.0.0",
536 "bare-events": "^2.3.1",
537 "bare-hrtime": "^2.0.0",
538 "bare-os": "^3.7.1",
539 "bare-pipe": "^4.0.0",
540 "bare-signals": "^4.0.0",
541 "bare-tty": "^5.0.0"
542 }
543 },
544 "node_modules/bare-semver": {
545 "version": "1.0.2",
546 "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.2.tgz",
547 "integrity": "sha512-ESVaN2nzWhcI5tf3Zzcq9aqCZ676VWzqw07eEZ0qxAcEOAFYBa0pWq8sK34OQeHLY3JsfKXZS9mDyzyxGjeLzA==",
548 "license": "Apache-2.0"
549 },
550 "node_modules/bare-signals": {
551 "version": "4.2.0",
552 "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz",
553 "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==",
554 "dev": true,
555 "license": "Apache-2.0",
556 "dependencies": {
557 "bare-events": "^2.5.3",
558 "bare-os": "^3.3.1"
559 },
560 "engines": {
561 "bare": ">=1.7.0"
562 }
563 },
564 "node_modules/bare-stream": {
565 "version": "2.10.0",
566 "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.10.0.tgz",
567 "integrity": "sha512-DOPZF/DDcDruKDA43cOw6e9Quq5daua7ygcAwJE/pKJsRWhgSSemi7qVNGE5kyDIxIeN1533G/zfbvWX7Wcb9w==",
568 "license": "Apache-2.0",
569 "dependencies": {
570 "streamx": "^2.25.0",
571 "teex": "^1.0.1"
572 },
573 "peerDependencies": {
574 "bare-buffer": "*",
575 "bare-events": "*"
576 },
577 "peerDependenciesMeta": {
578 "bare-buffer": {
579 "optional": true
580 },
581 "bare-events": {
582 "optional": true
583 }
584 }
585 },
586 "node_modules/bare-stylize": {
587 "version": "0.0.1",
588 "resolved": "https://registry.npmjs.org/bare-stylize/-/bare-stylize-0.0.1.tgz",
589 "integrity": "sha512-l3MjmIl476bWijYWf3RbE+osl4iuXSOMudzp0vAqzIK7gPgn/+G3oAxp8Oin9CFF911KBP0LO9kts8Ci8mGZaQ==",
590 "dev": true,
591 "license": "Apache-2.0",
592 "dependencies": {
593 "bare-ansi-escapes": "^2.2.3",
594 "bare-process": "^4.2.1"
595 }
596 },
597 "node_modules/bare-subprocess": {
598 "version": "5.2.3",
599 "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.3.tgz",
600 "integrity": "sha512-07wwswlV7M3sC9IykbZRZ/jHAkrXFWVLqdBWGv1y0ojCimtRD9hGwxdHmR5FUFmDUZLNsBmTYJNQqgio5+A85Q==",
601 "dev": true,
602 "license": "Apache-2.0",
603 "dependencies": {
604 "bare-env": "^3.0.0",
605 "bare-events": "^2.5.4",
606 "bare-os": "^3.0.1",
607 "bare-pipe": "^4.0.0",
608 "bare-url": "^2.2.2"
609 },
610 "engines": {
611 "bare": ">=1.7.0"
612 },
613 "peerDependencies": {
614 "bare-buffer": "*"
615 },
616 "peerDependenciesMeta": {
617 "bare-buffer": {
618 "optional": true
619 }
620 }
621 },
622 "node_modules/bare-tcp": {
623 "version": "2.2.7",
624 "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.7.tgz",
625 "integrity": "sha512-rjpqNQ2cOCkNo3NeYA/W4GTK3DRkl8sDHO3uos+AEswUjLC8XXMQF8WrJCSjlIowCbS6NUVxKE92X5RGXjyefg==",
626 "dev": true,
627 "license": "Apache-2.0",
628 "dependencies": {
629 "bare-dns": "^2.0.4",
630 "bare-events": "^2.5.4",
631 "bare-stream": "^2.6.4"
632 },
633 "engines": {
634 "bare": ">=1.16.0"
635 }
636 },
637 "node_modules/bare-tls": {
638 "version": "2.2.1",
639 "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.2.1.tgz",
640 "integrity": "sha512-hZ+ZqwrUO4dyH7/6WYkYWjgAFNJKjzwEYJiDaMnMs+eRleBDjQ3CvNZawpkw0Ar9jnM9NZK6+f6GqjkZ2FLGmQ==",
641 "dev": true,
642 "license": "Apache-2.0",
643 "dependencies": {
644 "bare-net": "^2.0.1",
645 "bare-stream": "^2.6.4"
646 },
647 "engines": {
648 "bare": ">=1.7.0"
649 }
650 },
651 "node_modules/bare-tty": {
652 "version": "5.0.3",
653 "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.0.3.tgz",
654 "integrity": "sha512-jW24RBWRZOMHuSEWC9mh8wUKO5WUNl0UWUTNPn3yZ8qkOqwa8vaV2dR3a+BvblONPk7V35wuvK9P8508+judAg==",
655 "dev": true,
656 "license": "Apache-2.0",
657 "dependencies": {
658 "bare-events": "^2.2.0",
659 "bare-signals": "^4.0.0",
660 "bare-stream": "^2.0.0"
661 },
662 "engines": {
663 "bare": ">=1.16.0"
664 }
665 },
666 "node_modules/bare-type": {
667 "version": "1.1.0",
668 "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz",
669 "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==",
670 "license": "Apache-2.0",
671 "engines": {
672 "bare": ">=1.2.0"
673 }
674 },
675 "node_modules/bare-url": {
676 "version": "2.4.0",
677 "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz",
678 "integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==",
679 "license": "Apache-2.0",
680 "peer": true,
681 "dependencies": {
682 "bare-path": "^3.0.0"
683 }
684 },
685 "node_modules/bare-utils": {
686 "version": "1.6.0",
687 "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.6.0.tgz",
688 "integrity": "sha512-WhQEIkkAxkSnW7u1QgrI0AfNm5JpMruETXeYsb5qnkBJ0TTfNKygZmsh6rkoHBANaV+C/7Jed7bJP9OmEHG7rQ==",
689 "dev": true,
690 "license": "Apache-2.0",
691 "dependencies": {
692 "bare-debug-log": "^2.0.0",
693 "bare-encoding": "^1.0.0",
694 "bare-format": "^1.0.0",
695 "bare-inspect": "^3.0.0",
696 "bare-stylize": "^0.0.1",
697 "bare-type": "^1.0.6"
698 }
699 },
700 "node_modules/bare-v8-to-istanbul": {
701 "version": "1.0.2",
702 "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz",
703 "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==",
704 "dev": true,
705 "license": "Apache-2.0",
706 "dependencies": {
707 "bare-assert": "^1.0.2",
708 "bare-fs": "^4.1.2",
709 "bare-module": "^6.1.2",
710 "bare-path": "^3.0.0",
711 "bare-process": "^4.2.0",
712 "bare-url": "^2.1.5",
713 "bare-utils": "^1.2.0",
714 "v8-to-istanbul": "^9.3.0",
715 "which-runtime": "^1.2.1"
716 }
717 },
718 "node_modules/bare-ws": {
719 "version": "2.1.0",
720 "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.1.0.tgz",
721 "integrity": "sha512-2gEWlPK9iyBchACdIY6oQXgmDz3KLrChdwrPgmU3IVXOFTnxTqSUT27WE/+izd4QojHj/SsqDkQiD2HDvuTdAA==",
722 "dev": true,
723 "license": "Apache-2.0",
724 "dependencies": {
725 "bare-crypto": "^1.2.0",
726 "bare-events": "^2.3.1",
727 "bare-http1": "^4.0.0",
728 "bare-https": "^2.0.0",
729 "bare-stream": "^2.1.2"
730 },
731 "peerDependencies": {
732 "bare-buffer": "*",
733 "bare-url": "*"
734 },
735 "peerDependenciesMeta": {
736 "bare-buffer": {
737 "optional": true
738 },
739 "bare-url": {
740 "optional": true
741 }
742 }
743 },
744 "node_modules/big-sparse-array": {
745 "version": "1.0.3",
746 "resolved": "https://registry.npmjs.org/big-sparse-array/-/big-sparse-array-1.0.3.tgz",
747 "integrity": "sha512-6RjV/3mSZORlMdpUaQ6rUSpG637cZm0//E54YYGtQg1c1O+AbZP8UTdJ/TchsDZcTVLmyWZcseBfp2HBeXUXOQ==",
748 "license": "MIT"
749 },
750 "node_modules/bip39-mnemonic": {
751 "version": "2.5.0",
752 "resolved": "https://registry.npmjs.org/bip39-mnemonic/-/bip39-mnemonic-2.5.0.tgz",
753 "integrity": "sha512-hgCxFHN129ta0bHl9VPykoqCPagBUr2oaXjfSbfeXAPe+y/byxwjPFFEFTMNO/1T/GSoMP1cOXHa2ZaprHG1kg==",
754 "license": "Apache-2.0",
755 "dependencies": {
756 "b4a": "^1.6.6",
757 "nanoassert": "^2.0.0",
758 "sodium-universal": "^5.0.1"
759 }
760 },
761 "node_modules/bits-to-bytes": {
762 "version": "1.3.0",
763 "resolved": "https://registry.npmjs.org/bits-to-bytes/-/bits-to-bytes-1.3.0.tgz",
764 "integrity": "sha512-OJoHTpFXS9bXHBCekGTByf3MqM8CGblBDIduKQeeVVeiU9dDWywSSirXIBYGgg3d1zbVuvnMa1vD4r6PA0kOKg==",
765 "license": "ISC",
766 "dependencies": {
767 "b4a": "^1.5.0"
768 }
769 },
770 "node_modules/blind-pairing": {
771 "version": "2.3.1",
772 "resolved": "https://registry.npmjs.org/blind-pairing/-/blind-pairing-2.3.1.tgz",
773 "integrity": "sha512-E678Mi6n07Yc8iRtgwzQLmk9EgHmYHBRzKDxpkdkS+Sh476RQ88iv75cvRkPp62rd7nsrP4lqku8uU9EeKGLiA==",
774 "license": "Apache-2.0",
775 "dependencies": {
776 "b4a": "^1.6.4",
777 "blind-pairing-core": "^2.0.0",
778 "debounceify": "^1.1.0",
779 "hypercore-crypto": "^3.4.0",
780 "is-options": "^1.0.2",
781 "ready-resource": "^1.0.0",
782 "safety-catch": "^1.0.2",
783 "xache": "^1.2.0"
784 }
785 },
786 "node_modules/blind-pairing-core": {
787 "version": "2.10.0",
788 "resolved": "https://registry.npmjs.org/blind-pairing-core/-/blind-pairing-core-2.10.0.tgz",
789 "integrity": "sha512-XBfaQ7yP0eFPwTJEtml3GQ7bFrA43fMBSsLPxL7zXtsnLbempYK9O0yTaL1UxCARh2X1+OdzUqvwidVBpnePfg==",
790 "license": "Apache-2.0",
791 "dependencies": {
792 "b4a": "^1.6.4",
793 "bare-events": "^2.5.0",
794 "bogon": "^1.1.0",
795 "compact-encoding": "^2.17.0",
796 "hypercore-crypto": "^3.4.0",
797 "sodium-universal": "^5.0.1",
798 "tiny-buffer-map": "^1.1.1"
799 }
800 },
801 "node_modules/blind-relay": {
802 "version": "1.4.0",
803 "resolved": "https://registry.npmjs.org/blind-relay/-/blind-relay-1.4.0.tgz",
804 "integrity": "sha512-6xt7fDfCs6eGmNNym6I9N42jmjcMQn2qwwOVnkP9ZnrkXFk6c4/tdO1xqRmDEzKzV8gigd+DVdCUG/RUYnen7Q==",
805 "license": "Apache-2.0",
806 "dependencies": {
807 "b4a": "^1.6.4",
808 "bare-events": "^2.2.0",
809 "bits-to-bytes": "^1.3.0",
810 "compact-encoding": "^2.12.0",
811 "compact-encoding-bitfield": "^1.0.0",
812 "protomux": "^3.5.1",
813 "sodium-universal": "^5.0.0",
814 "streamx": "^2.15.1"
815 }
816 },
817 "node_modules/bogon": {
818 "version": "1.2.0",
819 "resolved": "https://registry.npmjs.org/bogon/-/bogon-1.2.0.tgz",
820 "integrity": "sha512-FqOBr/1VMzCOsoJd+fzNUMarUYki2+TKt07A2+xaulsNx4r53iJ7MV5k0jbqg7W2U0CsLqxCZOrFibdG6h6HSg==",
821 "license": "MIT",
822 "dependencies": {
823 "compact-encoding": "^2.11.0",
824 "compact-encoding-net": "^1.2.0"
825 }
826 },
827 "node_modules/brittle": {
828 "version": "3.19.1",
829 "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz",
830 "integrity": "sha512-4Ted1Mt9o9B6oIA6ImJJCtB/Fv++ZO3IDNQgRcHOXWSYGRUidgxchaGrUBaRn+4mlneXrgInPkCPzi0jO8qKbA==",
831 "dev": true,
832 "license": "Apache-2.0",
833 "dependencies": {
834 "b4a": "^1.6.0",
835 "bare-assert": "^1.0.2",
836 "bare-cov": "^1.1.0",
837 "bare-fs": "^4.1.6",
838 "bare-os": "^3.6.1",
839 "bare-path": "^3.0.0",
840 "bare-process": "^4.2.1",
841 "bare-subprocess": "^5.0.0",
842 "bare-url": "^2.1.6",
843 "error-stack-parser": "^2.1.4",
844 "globbie": "^1.0.2",
845 "paparam": "^1.6.2",
846 "same-object": "^1.0.2",
847 "test-tmp": "^1.4.0",
848 "tmatch": "^5.0.0"
849 },
850 "bin": {
851 "brittle": "bin/node.js",
852 "brittle-bare": "bin/bare.js",
853 "brittle-node": "bin/node.js",
854 "brittle-pear": "bin/pear.js"
855 }
856 },
857 "node_modules/codecs": {
858 "version": "3.1.0",
859 "resolved": "https://registry.npmjs.org/codecs/-/codecs-3.1.0.tgz",
860 "integrity": "sha512-Dqx8NwvBvnMeuPQdVKy/XEF71igjR5apxBvCGeV0pP1tXadOiaLvDTXt7xh+/5wI1ASB195mXQGJbw3Ml4YDWQ==",
861 "license": "MIT",
862 "dependencies": {
863 "b4a": "^1.6.3"
864 }
865 },
866 "node_modules/compact-encoding": {
867 "version": "2.19.1",
868 "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-2.19.1.tgz",
869 "integrity": "sha512-4J4FHc5UugLv2YMss1tL8OBZdgR0vVkV4EuXoAqFS4NbK4gz0Qp+E+VGUnwvDqmMOw2ce/H8duXh3rreC0Qccg==",
870 "license": "Apache-2.0",
871 "dependencies": {
872 "b4a": "^1.3.0"
873 }
874 },
875 "node_modules/compact-encoding-bitfield": {
876 "version": "1.0.0",
877 "resolved": "https://registry.npmjs.org/compact-encoding-bitfield/-/compact-encoding-bitfield-1.0.0.tgz",
878 "integrity": "sha512-3nMVKUg+PF72UHfainmCL8uKvyWfxsjqOtUY+HiMPGLPCTjnwzoKfFAMo1Ad7nwTPdjBqtGK5b3BOFTFW4EBTg==",
879 "license": "ISC",
880 "dependencies": {
881 "compact-encoding": "^2.4.1"
882 }
883 },
884 "node_modules/compact-encoding-net": {
885 "version": "1.2.0",
886 "resolved": "https://registry.npmjs.org/compact-encoding-net/-/compact-encoding-net-1.2.0.tgz",
887 "integrity": "sha512-LVXpNpF7PGQeHRVVLGgYWzuVoYAaDZvKUsUxRioGfkotzvOh4AzoQF1HBH3zMNaSnx7gJXuUr3hkjnijaH/Eng==",
888 "license": "ISC",
889 "dependencies": {
890 "compact-encoding": "^2.4.1"
891 }
892 },
893 "node_modules/convert-source-map": {
894 "version": "2.0.0",
895 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
896 "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
897 "dev": true,
898 "license": "MIT"
899 },
900 "node_modules/core-coupler": {
901 "version": "2.0.0",
902 "resolved": "https://registry.npmjs.org/core-coupler/-/core-coupler-2.0.0.tgz",
903 "integrity": "sha512-FJuEvsdCMwx0Wu+gFQ49rGCi8LCXh8kizHsCQwkdgPZFEFiF0z2HDvyIs+fPt5wMIfU2UVFDuN+dtpfbIxJE6g==",
904 "license": "Apache-2.0",
905 "dependencies": {
906 "safety-catch": "^1.0.2"
907 }
908 },
909 "node_modules/corestore": {
910 "version": "7.9.2",
911 "resolved": "https://registry.npmjs.org/corestore/-/corestore-7.9.2.tgz",
912 "integrity": "sha512-dyIktVSVLu0skZ1UM4yODbNbZ46dBpTHypzwlwET1btVuS3YIf66Zz8Tx8h3Xa8nbLO2USGqrYtvPYzSRj2wCA==",
913 "license": "MIT",
914 "dependencies": {
915 "b4a": "^1.6.7",
916 "hypercore": "^11.19.0",
917 "hypercore-crypto": "^3.4.2",
918 "hypercore-errors": "^1.4.0",
919 "hypercore-id-encoding": "^1.3.0",
920 "ready-resource": "^1.1.1",
921 "sodium-universal": "^5.0.1",
922 "which-runtime": "^1.2.1"
923 }
924 },
925 "node_modules/debounceify": {
926 "version": "1.1.0",
927 "resolved": "https://registry.npmjs.org/debounceify/-/debounceify-1.1.0.tgz",
928 "integrity": "sha512-eKuHDVfJVg+u/0nPy8P+fhnLgbyuTgVxuCRrS/R7EpDSMMkBDgSes41MJtSAY1F1hcqfHz3Zy/qpqHHIp/EhdA==",
929 "license": "MIT"
930 },
931 "node_modules/device-file": {
932 "version": "2.3.1",
933 "resolved": "https://registry.npmjs.org/device-file/-/device-file-2.3.1.tgz",
934 "integrity": "sha512-bmON44lwxJPle9N2OcH4tqM44pMGZKT8G6OkzXkz0urvqQ9LKkoQdTS6w0ztYfmLdUE27R4UUmx0+y2gEz4Jug==",
935 "license": "Apache-2.0",
936 "dependencies": {
937 "b4a": "^1.6.7",
938 "bare-fs": "^4.0.1",
939 "bare-path": "^3.0.0",
940 "fd-lock": "^2.1.0",
941 "fs-native-extensions": "^1.4.0",
942 "ready-resource": "^1.2.0"
943 }
944 },
945 "node_modules/dht-rpc": {
946 "version": "6.26.3",
947 "resolved": "https://registry.npmjs.org/dht-rpc/-/dht-rpc-6.26.3.tgz",
948 "integrity": "sha512-KuLfRv/hecUHipQcTXHpVv4/N4Jhpww5sLdsrn3Edm5oHwzK9SgNV34hNt7a2aVZCOcG5SfP4AvcQ7pI+y9YNg==",
949 "license": "MIT",
950 "dependencies": {
951 "adaptive-timeout": "^1.0.1",
952 "b4a": "^1.6.1",
953 "bare-events": "^2.2.0",
954 "compact-encoding": "^2.11.0",
955 "compact-encoding-net": "^1.2.0",
956 "fast-fifo": "^1.1.0",
957 "kademlia-routing-table": "^1.0.1",
958 "nat-sampler": "^1.0.1",
959 "sodium-universal": "^5.0.0",
960 "streamx": "^2.13.2",
961 "time-ordered-set": "^2.0.0",
962 "udx-native": "^1.5.3"
963 }
964 },
965 "node_modules/encryption-encoding": {
966 "version": "1.0.3",
967 "resolved": "https://registry.npmjs.org/encryption-encoding/-/encryption-encoding-1.0.3.tgz",
968 "integrity": "sha512-+SlKCeULnNnwBF2rGrJlSLYjwITwfjO8PLSHzue4yt+2DsVJUU8GakRkH4+mTaa3FzpxVjpTy1kKR2gYGctNWg==",
969 "license": "Apache-2.0",
970 "dependencies": {
971 "hyperschema": "^1.19.0"
972 }
973 },
974 "node_modules/error-stack-parser": {
975 "version": "2.1.4",
976 "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
977 "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
978 "dev": true,
979 "license": "MIT",
980 "dependencies": {
981 "stackframe": "^1.3.4"
982 }
983 },
984 "node_modules/events-universal": {
985 "version": "1.0.1",
986 "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
987 "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
988 "license": "Apache-2.0",
989 "dependencies": {
990 "bare-events": "^2.7.0"
991 }
992 },
993 "node_modules/fast-fifo": {
994 "version": "1.3.2",
995 "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
996 "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
997 "license": "MIT"
998 },
999 "node_modules/fd-lock": {
1000 "version": "2.1.1",
1001 "resolved": "https://registry.npmjs.org/fd-lock/-/fd-lock-2.1.1.tgz",
1002 "integrity": "sha512-H3VkcWFl39Rk0xBokDcUyBcVs6VrYTUo/DhDWMzJOF99wXWDAvmvq/GlLTS2NTehsznZhp3fXVyrtB2ldDXhwg==",
1003 "license": "Apache-2.0",
1004 "dependencies": {
1005 "bare-fs": "^4.5.0",
1006 "fs-native-extensions": "^1.4.4",
1007 "ready-resource": "^1.2.0",
1008 "resource-on-exit": "^1.0.0"
1009 }
1010 },
1011 "node_modules/flat-tree": {
1012 "version": "1.13.0",
1013 "resolved": "https://registry.npmjs.org/flat-tree/-/flat-tree-1.13.0.tgz",
1014 "integrity": "sha512-fT3HIuCPwHhFgJ20QYzDHgUG0zMmFg5cHvFiFo5h+QMSJ28TihsEVY0f8HGliuO+pOzmvjMx1odToeaEWkTnyQ==",
1015 "license": "MIT"
1016 },
1017 "node_modules/fs-native-extensions": {
1018 "version": "1.4.5",
1019 "resolved": "https://registry.npmjs.org/fs-native-extensions/-/fs-native-extensions-1.4.5.tgz",
1020 "integrity": "sha512-ekV0T//iDm4AvhOcuPaHpxub4DI7HvY5ucLJVDvi7T2J+NZkQ9S6MuvgP0yeQvoqNUaAGyLjVYb1905BF9bpmg==",
1021 "license": "Apache-2.0",
1022 "dependencies": {
1023 "require-addon": "^1.1.0",
1024 "which-runtime": "^1.2.0"
1025 }
1026 },
1027 "node_modules/generate-object-property": {
1028 "version": "2.0.0",
1029 "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-2.0.0.tgz",
1030 "integrity": "sha512-KwuURPyqn2Mz8DdV29pJwQu0Y7tcsbkULr82eeOcY/ZllFK6I9Wm8dsRByIu7CKWlFi9BdW1b3mcXMp/kQBQsw==",
1031 "license": "MIT",
1032 "dependencies": {
1033 "is-property": "^1.0.0"
1034 }
1035 },
1036 "node_modules/generate-string": {
1037 "version": "1.0.1",
1038 "resolved": "https://registry.npmjs.org/generate-string/-/generate-string-1.0.1.tgz",
1039 "integrity": "sha512-IfTY0dKZM43ACyGvXkbG7De7WY7MxTS5VO6Juhe8oJKpCmrYYXoqp/cJMskkpi0k9H8wuXq0H+eI898/BCqvXg==",
1040 "license": "MIT"
1041 },
1042 "node_modules/globbie": {
1043 "version": "1.0.3",
1044 "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz",
1045 "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==",
1046 "dev": true,
1047 "license": "Apache-2.0",
1048 "dependencies": {
1049 "bare-fs": "^4.1.2",
1050 "bare-path": "^3.0.0",
1051 "bare-process": "^4.2.1",
1052 "picomatch": "^4.0.2"
1053 }
1054 },
1055 "node_modules/hyperbee": {
1056 "version": "2.27.3",
1057 "resolved": "https://registry.npmjs.org/hyperbee/-/hyperbee-2.27.3.tgz",
1058 "integrity": "sha512-PXURH2U4juUZyJRKHTrY5z1zX851pmI1Q0jfv5F/hCIErDt/ND8jOZuxc3hfOLM9f0W3qJEDTMlV5AJBkVPy8w==",
1059 "license": "MIT",
1060 "dependencies": {
1061 "b4a": "^1.6.0",
1062 "codecs": "^3.0.0",
1063 "debounceify": "^1.0.0",
1064 "hypercore-errors": "^1.0.0",
1065 "mutexify": "^1.4.0",
1066 "protocol-buffers-encodings": "^1.2.0",
1067 "rache": "^1.0.0",
1068 "ready-resource": "^1.0.0",
1069 "resolve-reject-promise": "^1.1.0",
1070 "safety-catch": "^1.0.2",
1071 "streamx": "^2.12.4",
1072 "unslab": "^1.2.0"
1073 }
1074 },
1075 "node_modules/hypercore": {
1076 "version": "11.27.17",
1077 "resolved": "https://registry.npmjs.org/hypercore/-/hypercore-11.27.17.tgz",
1078 "integrity": "sha512-u4GBBDN2EmOk/vzGOQqDOFTN3UdOuSAIbWik6ZboMHVr21n5rSM2rK/FXwc28kW7JUBtbfNKY28Hi86rVFMzYw==",
1079 "license": "MIT",
1080 "dependencies": {
1081 "@hyperswarm/secret-stream": "^6.0.0",
1082 "b4a": "^1.1.0",
1083 "bare-events": "^2.2.0",
1084 "big-sparse-array": "^1.0.3",
1085 "compact-encoding": "^2.11.0",
1086 "fast-fifo": "^1.3.0",
1087 "flat-tree": "^1.9.0",
1088 "hypercore-crypto": "^3.2.1",
1089 "hypercore-errors": "^1.5.0",
1090 "hypercore-id-encoding": "^1.2.0",
1091 "hypercore-storage": "^2.0.0",
1092 "is-options": "^1.0.1",
1093 "nanoassert": "^2.0.0",
1094 "protomux": "^3.5.0",
1095 "quickbit-universal": "^2.2.0",
1096 "random-array-iterator": "^1.0.0",
1097 "safety-catch": "^1.0.1",
1098 "sodium-universal": "^5.0.1",
1099 "streamx": "^2.12.4",
1100 "unslab": "^1.3.0",
1101 "z32": "^1.0.0"
1102 }
1103 },
1104 "node_modules/hypercore-crypto": {
1105 "version": "3.6.1",
1106 "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.6.1.tgz",
1107 "integrity": "sha512-ltIz2uDwy9pO/ZGTvqcjzyBkvt6O4cVm4r/nNxh0GFs/RbQtqP/i4wCvLEdmU7ptgtnw7fI67WYD1aHPuv4OVA==",
1108 "license": "MIT",
1109 "dependencies": {
1110 "b4a": "^1.6.6",
1111 "compact-encoding": "^2.15.0",
1112 "sodium-universal": "^5.0.0"
1113 }
1114 },
1115 "node_modules/hypercore-errors": {
1116 "version": "1.5.0",
1117 "resolved": "https://registry.npmjs.org/hypercore-errors/-/hypercore-errors-1.5.0.tgz",
1118 "integrity": "sha512-5KQ/SuDxsvet+7qWA35Ay6zdD9WyAHQoyWHGcPUTbmJBd300gvNIJoi3oma7kp4TTCSzii6qYumNZe/s0j/saQ==",
1119 "license": "Apache-2.0",
1120 "dependencies": {
1121 "hypercore-id-encoding": "^1.3.0"
1122 }
1123 },
1124 "node_modules/hypercore-id-encoding": {
1125 "version": "1.3.0",
1126 "resolved": "https://registry.npmjs.org/hypercore-id-encoding/-/hypercore-id-encoding-1.3.0.tgz",
1127 "integrity": "sha512-W6sHdGo5h7LXEsoWfKf/KfuROZmZRQDlGqJF2EPHW+noCK66Vvr0+zE6cL0vqQi18s0kQPeN7Sq3QyR0Ytc2VQ==",
1128 "license": "Apache-2.0",
1129 "dependencies": {
1130 "b4a": "^1.5.3",
1131 "z32": "^1.0.0"
1132 }
1133 },
1134 "node_modules/hypercore-storage": {
1135 "version": "2.7.1",
1136 "resolved": "https://registry.npmjs.org/hypercore-storage/-/hypercore-storage-2.7.1.tgz",
1137 "integrity": "sha512-eT3s227nS17c8+HWjl6ge0l1lPk9NDtBzQH///tOSZjCG4gxk0QnjhlJVS5q1dcnNB54Gs75JKceP798IymvTQ==",
1138 "license": "Apache-2.0",
1139 "dependencies": {
1140 "b4a": "^1.6.7",
1141 "bare-fs": "^4.0.1",
1142 "bare-path": "^3.0.0",
1143 "compact-encoding": "^2.16.0",
1144 "device-file": "^2.1.2",
1145 "flat-tree": "^1.12.1",
1146 "hypercore-crypto": "^3.4.2",
1147 "hyperschema": "^1.7.0",
1148 "index-encoder": "^3.3.2",
1149 "resolve-reject-promise": "^1.0.0",
1150 "rocksdb-native": "^3.11.0",
1151 "scope-lock": "^1.2.4",
1152 "streamx": "^2.21.1"
1153 }
1154 },
1155 "node_modules/hyperdht": {
1156 "version": "6.29.6",
1157 "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.29.6.tgz",
1158 "integrity": "sha512-bMMtw02fhiALdsTVLNZ/VFmMZuEY8kxb0/Rprl6oYF4/TCS653i3FDjtJeRbqtnAzwAj6dVZ/EwhoNOSCFGaYQ==",
1159 "license": "MIT",
1160 "dependencies": {
1161 "@hyperswarm/secret-stream": "^6.6.2",
1162 "b4a": "^1.3.1",
1163 "bare-events": "^2.2.0",
1164 "blind-relay": "^1.3.0",
1165 "bogon": "^1.0.0",
1166 "compact-encoding": "^2.4.1",
1167 "dht-rpc": "^6.15.1",
1168 "hypercore-crypto": "^3.3.0",
1169 "hypercore-id-encoding": "^1.2.0",
1170 "noise-curve-ed": "^2.0.0",
1171 "noise-handshake": "^4.0.0",
1172 "record-cache": "^1.1.1",
1173 "safety-catch": "^1.0.1",
1174 "signal-promise": "^1.0.3",
1175 "sodium-universal": "^5.0.1",
1176 "streamx": "^2.16.1",
1177 "unslab": "^1.3.0",
1178 "xache": "^1.1.0"
1179 },
1180 "bin": {
1181 "hyperdht": "bin.js"
1182 }
1183 },
1184 "node_modules/hyperschema": {
1185 "version": "1.20.1",
1186 "resolved": "https://registry.npmjs.org/hyperschema/-/hyperschema-1.20.1.tgz",
1187 "integrity": "sha512-7gnvaTNUs8FqdrU3NNE6nzhOneGyDlUlpROu9YFlHA61fE8ppKKf2gLZjZjsx/enNt+VFN4ITxy8ijypfyWBLw==",
1188 "license": "Apache-2.0",
1189 "dependencies": {
1190 "bare-fs": "^4.0.1",
1191 "compact-encoding": "^2.19.0",
1192 "generate-object-property": "^2.0.0",
1193 "generate-string": "^1.0.1"
1194 }
1195 },
1196 "node_modules/hyperswarm": {
1197 "version": "4.17.0",
1198 "resolved": "https://registry.npmjs.org/hyperswarm/-/hyperswarm-4.17.0.tgz",
1199 "integrity": "sha512-oe86sK961Ueg7rvDN/veFwG8xH+Iv6vObPhGDkPJcDVxk/NduW41ZhAcVDnHzRbm7S0eLU7WaDUvehOYoKSpRQ==",
1200 "license": "MIT",
1201 "dependencies": {
1202 "b4a": "^1.3.1",
1203 "bare-events": "^2.2.0",
1204 "hyperdht": "^6.21.0",
1205 "safety-catch": "^1.0.2",
1206 "shuffled-priority-queue": "^2.1.0",
1207 "streamx": "^2.22.1",
1208 "unslab": "^1.3.0"
1209 }
1210 },
1211 "node_modules/index-encoder": {
1212 "version": "3.5.0",
1213 "resolved": "https://registry.npmjs.org/index-encoder/-/index-encoder-3.5.0.tgz",
1214 "integrity": "sha512-idZ1cxtZz2dRV6rUiaP9Xo99UjXbSzjcMacoQmxUMu/A7fEQcNPngvwDJYeWelQUS5XFlY71/or70lKn6XnwbQ==",
1215 "license": "Apache-2.0",
1216 "dependencies": {
1217 "b4a": "^1.6.4"
1218 }
1219 },
1220 "node_modules/is-options": {
1221 "version": "1.0.2",
1222 "resolved": "https://registry.npmjs.org/is-options/-/is-options-1.0.2.tgz",
1223 "integrity": "sha512-u+Ai74c8Q74aS8BuHwPdI1jptGOT1FQXgCq8/zv0xRuE+wRgSMEJLj8lVO8Zp9BeGb29BXY6AsNPinfqjkr7Fg==",
1224 "license": "MIT",
1225 "dependencies": {
1226 "b4a": "^1.1.1"
1227 }
1228 },
1229 "node_modules/is-property": {
1230 "version": "1.0.2",
1231 "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
1232 "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
1233 "license": "MIT"
1234 },
1235 "node_modules/kademlia-routing-table": {
1236 "version": "1.0.6",
1237 "resolved": "https://registry.npmjs.org/kademlia-routing-table/-/kademlia-routing-table-1.0.6.tgz",
1238 "integrity": "sha512-Ve6jwIlUCYvUzBnXnzVRHDZCFgXURW9gmF3r7n05kZs/2rNbLHXwGdcq0qIaSwdmJCvtosgR4JensnVU65hzNQ==",
1239 "license": "MIT",
1240 "dependencies": {
1241 "bare-events": "^2.2.0"
1242 }
1243 },
1244 "node_modules/keet-identity-key": {
1245 "version": "3.1.0",
1246 "resolved": "git+ssh://git@github.com/holepunchto/keet-identity-key.git#f02311afbeac10e73756539e809684349740631f",
1247 "license": "Apache-2.0",
1248 "dependencies": {
1249 "b4a": "^1.6.4",
1250 "bip39-mnemonic": "^2.2.0",
1251 "compact-encoding": "^2.13.0",
1252 "nanoassert": "^2.0.0",
1253 "sodium-hmac": "^2.0.1",
1254 "sodium-universal": "^5.0.1"
1255 }
1256 },
1257 "node_modules/mutexify": {
1258 "version": "1.4.0",
1259 "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz",
1260 "integrity": "sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==",
1261 "license": "MIT",
1262 "dependencies": {
1263 "queue-tick": "^1.0.0"
1264 }
1265 },
1266 "node_modules/nanoassert": {
1267 "version": "2.0.0",
1268 "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz",
1269 "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==",
1270 "license": "ISC"
1271 },
1272 "node_modules/nat-sampler": {
1273 "version": "1.0.1",
1274 "resolved": "https://registry.npmjs.org/nat-sampler/-/nat-sampler-1.0.1.tgz",
1275 "integrity": "sha512-yQvyNN7xbqR8crTKk3U8gRgpcV1Az+vfCEijiHu9oHHsnIl8n3x+yXNHl42M6L3czGynAVoOT9TqBfS87gDdcw==",
1276 "license": "MIT"
1277 },
1278 "node_modules/noise-curve-ed": {
1279 "version": "2.1.0",
1280 "resolved": "https://registry.npmjs.org/noise-curve-ed/-/noise-curve-ed-2.1.0.tgz",
1281 "integrity": "sha512-zAzJx+VwZM3w6EA1hTmDhJfvAnCeBQn/1FAeZ0LtGxCcCtlAK/uJXQVF/eDVUOaAZ286lHlx77WJ+qj9SmsRRg==",
1282 "license": "ISC",
1283 "dependencies": {
1284 "b4a": "^1.1.0",
1285 "nanoassert": "^2.0.0",
1286 "sodium-universal": "^5.0.0"
1287 }
1288 },
1289 "node_modules/noise-handshake": {
1290 "version": "4.2.0",
1291 "resolved": "https://registry.npmjs.org/noise-handshake/-/noise-handshake-4.2.0.tgz",
1292 "integrity": "sha512-9O/VTNX/E2/AToyMTTDU0J/4WhaXMTdqc2DHs9vf+snoZ0cenSBq0dNYTVV1snYYEkmo6QeRrYMxtqtoYnY+LA==",
1293 "license": "Apache-2.0",
1294 "dependencies": {
1295 "b4a": "^1.1.0",
1296 "nanoassert": "^2.0.0",
1297 "sodium-universal": "^5.0.0"
1298 }
1299 },
1300 "node_modules/paparam": {
1301 "version": "1.10.1",
1302 "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.1.tgz",
1303 "integrity": "sha512-viyQI64VIja0Za3njIzhoEP8ZVkgPowhZPuG0E96NwBfYJ6ZIyrrlhWGtFPkdN7eYLl2L8CTWL+wla0evm1KKQ==",
1304 "dev": true,
1305 "license": "Apache-2.0"
1306 },
1307 "node_modules/picomatch": {
1308 "version": "4.0.4",
1309 "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
1310 "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
1311 "dev": true,
1312 "license": "MIT",
1313 "engines": {
1314 "node": ">=12"
1315 },
1316 "funding": {
1317 "url": "https://github.com/sponsors/jonschlinkert"
1318 }
1319 },
1320 "node_modules/protocol-buffers-encodings": {
1321 "version": "1.2.0",
1322 "resolved": "https://registry.npmjs.org/protocol-buffers-encodings/-/protocol-buffers-encodings-1.2.0.tgz",
1323 "integrity": "sha512-daeNPuKh1NlLD1uDfbLpD+xyUTc07nEtfHwmBZmt/vH0B7VOM+JOCOpDcx9ZRpqHjAiIkGqyTDi+wfGSl17R9w==",
1324 "license": "MIT",
1325 "dependencies": {
1326 "b4a": "^1.6.0",
1327 "signed-varint": "^2.0.1",
1328 "varint": "5.0.0"
1329 }
1330 },
1331 "node_modules/protomux": {
1332 "version": "3.10.1",
1333 "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.10.1.tgz",
1334 "integrity": "sha512-jgBqx8ZyaBWea/DFG4eOu1scOaeBwcnagiRC1XFVrjeGt7oAb0Pk5udPpBUpJ4DJBRjra50jD6YcZiQQTRqaaA==",
1335 "license": "MIT",
1336 "dependencies": {
1337 "b4a": "^1.3.1",
1338 "compact-encoding": "^2.5.1",
1339 "queue-tick": "^1.0.0",
1340 "safety-catch": "^1.0.1",
1341 "unslab": "^1.3.0"
1342 }
1343 },
1344 "node_modules/protomux-wakeup": {
1345 "version": "2.9.0",
1346 "resolved": "https://registry.npmjs.org/protomux-wakeup/-/protomux-wakeup-2.9.0.tgz",
1347 "integrity": "sha512-K93WhS9qIRL8WAQPU2V3PxdpQ1oRlQCFyCyIYInoq/iRkcuO1BegRERWo7SS1Tcme14VKGWOr/IivjjYnHan3Q==",
1348 "license": "Apache-2.0",
1349 "dependencies": {
1350 "b4a": "^1.6.7",
1351 "hypercore-crypto": "^3.5.0",
1352 "hyperschema": "^1.10.4",
1353 "protomux": "^3.10.1"
1354 }
1355 },
1356 "node_modules/queue-tick": {
1357 "version": "1.0.1",
1358 "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
1359 "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==",
1360 "license": "MIT"
1361 },
1362 "node_modules/quickbit-native": {
1363 "version": "2.4.8",
1364 "resolved": "https://registry.npmjs.org/quickbit-native/-/quickbit-native-2.4.8.tgz",
1365 "integrity": "sha512-FcCcqI+nIAWGknqhtrYT5TSD7t/N+Xd8ctM+2PrIIBuwOi5hx0SxAvuPtzLIEMfT/2h9+fhBakUe2uALOHX6yw==",
1366 "license": "Apache-2.0",
1367 "optional": true,
1368 "dependencies": {
1369 "require-addon": "^1.1.0"
1370 }
1371 },
1372 "node_modules/quickbit-universal": {
1373 "version": "2.2.0",
1374 "resolved": "https://registry.npmjs.org/quickbit-universal/-/quickbit-universal-2.2.0.tgz",
1375 "integrity": "sha512-w02i1R8n7+6pEKTud8DfF8zbFY9o7RtPlUc3jWbtCkDKvhbx/AvV7oNnz4/TcmsPGpSJS+fq5Ud6RH6+YPvSGg==",
1376 "license": "ISC",
1377 "dependencies": {
1378 "b4a": "^1.6.0",
1379 "simdle-universal": "^1.1.0"
1380 },
1381 "optionalDependencies": {
1382 "quickbit-native": "^2.2.0"
1383 }
1384 },
1385 "node_modules/rache": {
1386 "version": "1.0.0",
1387 "resolved": "https://registry.npmjs.org/rache/-/rache-1.0.0.tgz",
1388 "integrity": "sha512-e0k0g0w/8jOCB+7YqCIlOa+OJ38k0wrYS4x18pMSmqOvLKoyhmMhmQyCcvfY6VaP8D75cqkEnlakXs+RYYLqNg==",
1389 "license": "Apache-2.0"
1390 },
1391 "node_modules/random-access-memory": {
1392 "version": "6.2.1",
1393 "resolved": "https://registry.npmjs.org/random-access-memory/-/random-access-memory-6.2.1.tgz",
1394 "integrity": "sha512-hUeu1PbGLmWeyze9LwwSNaqloivNYjFsARIYxRdgUgn0wrdvMG+RszrfTG8814zfcXOgy4pFO2TpX/Cl3hRO4w==",
1395 "dev": true,
1396 "license": "MIT",
1397 "dependencies": {
1398 "b4a": "^1.6.0",
1399 "is-options": "^1.0.2",
1400 "random-access-storage": "^3.0.0"
1401 }
1402 },
1403 "node_modules/random-access-storage": {
1404 "version": "3.0.2",
1405 "resolved": "https://registry.npmjs.org/random-access-storage/-/random-access-storage-3.0.2.tgz",
1406 "integrity": "sha512-Es9maUyWdJXWKckKy9s1+vT+DEgAt+PBb9lxPaake/0EDUsHehloKGv9v1zimS2V3gpFAcQXubvc1Rgci2sDPQ==",
1407 "dev": true,
1408 "license": "MIT",
1409 "dependencies": {
1410 "bare-events": "^2.2.0",
1411 "queue-tick": "^1.0.0"
1412 }
1413 },
1414 "node_modules/random-array-iterator": {
1415 "version": "1.0.0",
1416 "resolved": "https://registry.npmjs.org/random-array-iterator/-/random-array-iterator-1.0.0.tgz",
1417 "integrity": "sha512-u7xCM93XqKEvPTP6xZp2ehttcAemKnh73oKNf1FvzuVCfpt6dILDt1Kxl1LeBjm2iNIeR49VGFhy4Iz3yOun+Q==",
1418 "license": "MIT"
1419 },
1420 "node_modules/ready-resource": {
1421 "version": "1.2.0",
1422 "resolved": "https://registry.npmjs.org/ready-resource/-/ready-resource-1.2.0.tgz",
1423 "integrity": "sha512-nfcco/8iAFV0M+2PYnmIc+/xY0iRb35d42HFHQ7AfjulbGEAFa+XWpByfwSyeVeiBoMLLFVMv1HixxNCqzSQ1g==",
1424 "license": "MIT",
1425 "dependencies": {
1426 "bare-events": "^2.2.0"
1427 }
1428 },
1429 "node_modules/record-cache": {
1430 "version": "1.2.0",
1431 "resolved": "https://registry.npmjs.org/record-cache/-/record-cache-1.2.0.tgz",
1432 "integrity": "sha512-kyy3HWCez2WrotaL3O4fTn0rsIdfRKOdQQcEJ9KpvmKmbffKVvwsloX063EgRUlpJIXHiDQFhJcTbZequ2uTZw==",
1433 "license": "MIT",
1434 "dependencies": {
1435 "b4a": "^1.3.1"
1436 }
1437 },
1438 "node_modules/refcounter": {
1439 "version": "1.0.0",
1440 "resolved": "https://registry.npmjs.org/refcounter/-/refcounter-1.0.0.tgz",
1441 "integrity": "sha512-1WosVzUy0kPUaPMEtlNDwm99UsteALIhXXR8rerELoa63WkYIXAl0hxgwPFrIYBRWZPGUyekQ04FRtPJ7dHk9w==",
1442 "license": "Apache-2.0"
1443 },
1444 "node_modules/require-addon": {
1445 "version": "1.2.0",
1446 "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz",
1447 "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==",
1448 "license": "Apache-2.0",
1449 "dependencies": {
1450 "bare-addon-resolve": "^1.3.0"
1451 },
1452 "engines": {
1453 "bare": ">=1.10.0"
1454 }
1455 },
1456 "node_modules/resolve-reject-promise": {
1457 "version": "1.1.0",
1458 "resolved": "https://registry.npmjs.org/resolve-reject-promise/-/resolve-reject-promise-1.1.0.tgz",
1459 "integrity": "sha512-LWsTOA91AqzBTjSGgX79Tc130pwcBK6xjpJEO+qRT5IKZ6bGnHKcc8QL3upUBcWuU8OTIDzKK2VNSwmmlqvAVg==",
1460 "license": "MIT"
1461 },
1462 "node_modules/resource-on-exit": {
1463 "version": "1.0.0",
1464 "resolved": "https://registry.npmjs.org/resource-on-exit/-/resource-on-exit-1.0.0.tgz",
1465 "integrity": "sha512-ViJwJAknCkLRJRPR+9SISQQ7R5eRgtdIHLJsM2hHx1MweAJbJxJ5XnMjjq0Lc7ZGv44ufzAqds1nKxiVkdy4ag==",
1466 "license": "Apache-2.0"
1467 },
1468 "node_modules/rocksdb-native": {
1469 "version": "3.15.0",
1470 "resolved": "https://registry.npmjs.org/rocksdb-native/-/rocksdb-native-3.15.0.tgz",
1471 "integrity": "sha512-czYx0hOzKAy+HYmJROZm7thvJ1tHFWHNkGI3nomumXWzrJghJdrWo4MIhehGX4OTff0lnOCoB2zu6ekzIKY0rQ==",
1472 "license": "Apache-2.0",
1473 "dependencies": {
1474 "compact-encoding": "^2.15.0",
1475 "ready-resource": "^1.0.0",
1476 "refcounter": "^1.0.0",
1477 "require-addon": "^1.0.2",
1478 "resolve-reject-promise": "^1.1.0",
1479 "signal-promise": "^1.0.3",
1480 "streamx": "^2.16.1"
1481 },
1482 "engines": {
1483 "bare": ">=1.16.0"
1484 }
1485 },
1486 "node_modules/safety-catch": {
1487 "version": "1.0.3",
1488 "resolved": "https://registry.npmjs.org/safety-catch/-/safety-catch-1.0.3.tgz",
1489 "integrity": "sha512-Zq+J1TefpoEq/HTUabo0YXX5MNvttjWYODGohgPBO2jfko8Wqx3JYMgE823szDFVamdH5PlpByvfiWScTdSYDA==",
1490 "license": "MIT"
1491 },
1492 "node_modules/same-object": {
1493 "version": "1.0.2",
1494 "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz",
1495 "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==",
1496 "dev": true,
1497 "license": "MIT"
1498 },
1499 "node_modules/scope-lock": {
1500 "version": "1.2.4",
1501 "resolved": "https://registry.npmjs.org/scope-lock/-/scope-lock-1.2.4.tgz",
1502 "integrity": "sha512-BpSd8VCuCxW9ZitcdIC/vjs3gMaP9bRBL5nkHcyfX2VrS52n13/rHuBA2xJ/S/4DPuRdAO/Bk8pWd8eD/gHCIA==",
1503 "license": "Apache-2.0"
1504 },
1505 "node_modules/shuffled-priority-queue": {
1506 "version": "2.1.0",
1507 "resolved": "https://registry.npmjs.org/shuffled-priority-queue/-/shuffled-priority-queue-2.1.0.tgz",
1508 "integrity": "sha512-xhdh7fHyMsr0m/w2kDfRJuBFRS96b9l8ZPNWGaQ+PMvnUnZ/Eh+gJJ9NsHBd7P9k0399WYlCLzsy18EaMfyadA==",
1509 "license": "MIT",
1510 "dependencies": {
1511 "unordered-set": "^2.0.1"
1512 }
1513 },
1514 "node_modules/signal-promise": {
1515 "version": "1.0.3",
1516 "resolved": "https://registry.npmjs.org/signal-promise/-/signal-promise-1.0.3.tgz",
1517 "integrity": "sha512-WBgv0UnIq2C+Aeh0/n+IRpP6967eIx9WpynTUoiW3isPpfe1zu2LJzyfXdo9Tgef8yR/sGjcMvoUXD7EYdiz+g==",
1518 "license": "MIT"
1519 },
1520 "node_modules/signed-varint": {
1521 "version": "2.0.1",
1522 "resolved": "https://registry.npmjs.org/signed-varint/-/signed-varint-2.0.1.tgz",
1523 "integrity": "sha512-abgDPg1106vuZZOvw7cFwdCABddfJRz5akcCcchzTbhyhYnsG31y4AlZEgp315T7W3nQq5P4xeOm186ZiPVFzw==",
1524 "license": "MIT",
1525 "dependencies": {
1526 "varint": "~5.0.0"
1527 }
1528 },
1529 "node_modules/simdle-native": {
1530 "version": "1.3.9",
1531 "resolved": "https://registry.npmjs.org/simdle-native/-/simdle-native-1.3.9.tgz",
1532 "integrity": "sha512-Isc8sP4OiiIU0mpslD4GHEnR0VQWvR/54WN7YtwEDkNdTJVWtpmvsSvsgRlw5BNGxdYXlVRegdnrSu10H/PhvA==",
1533 "license": "Apache-2.0",
1534 "optional": true,
1535 "dependencies": {
1536 "b4a": "^1.6.0",
1537 "require-addon": "^1.1.0"
1538 }
1539 },
1540 "node_modules/simdle-universal": {
1541 "version": "1.1.2",
1542 "resolved": "https://registry.npmjs.org/simdle-universal/-/simdle-universal-1.1.2.tgz",
1543 "integrity": "sha512-3n3w1bs+uwgHKQjt6arez83EywNlhZzYvNOhvAASTl/8KqNIcqr6aHyGt3JRlfuUC7iB0tomJRPlJ2cRGIpBzA==",
1544 "license": "ISC",
1545 "dependencies": {
1546 "b4a": "^1.6.0"
1547 },
1548 "optionalDependencies": {
1549 "simdle-native": "^1.1.1"
1550 }
1551 },
1552 "node_modules/sodium-hmac": {
1553 "version": "2.1.0",
1554 "resolved": "https://registry.npmjs.org/sodium-hmac/-/sodium-hmac-2.1.0.tgz",
1555 "integrity": "sha512-1g7d7v+/0g/XXSX9sp0++X1QB6M1knhomnsFiN7B42b+rV0VhQeyiRzoWtp7vYGLkzmLY77tWNnCef3juGRwfQ==",
1556 "license": "Apache-2.0",
1557 "dependencies": {
1558 "b4a": "^1.6.6",
1559 "sodium-universal": "^5.0.1"
1560 }
1561 },
1562 "node_modules/sodium-native": {
1563 "version": "4.3.3",
1564 "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.3.tgz",
1565 "integrity": "sha512-OnxSlN3uyY8D0EsLHpmm2HOFmKddQVvEMmsakCrXUzSd8kjjbzL413t4ZNF3n0UxSwNgwTyUvkmZHTfuCeiYSw==",
1566 "license": "MIT",
1567 "dependencies": {
1568 "require-addon": "^1.1.0"
1569 }
1570 },
1571 "node_modules/sodium-secretstream": {
1572 "version": "1.2.0",
1573 "resolved": "https://registry.npmjs.org/sodium-secretstream/-/sodium-secretstream-1.2.0.tgz",
1574 "integrity": "sha512-q/DbraNFXm1KfCiiZvapmz5UC3OlpirYFIvBK2MhGaOFSb3gRyk8OXTi17UI9SGfshQNCpsVvlopogbzZNyW6Q==",
1575 "license": "MIT",
1576 "dependencies": {
1577 "b4a": "^1.1.1",
1578 "sodium-universal": "^5.0.0"
1579 }
1580 },
1581 "node_modules/sodium-universal": {
1582 "version": "5.0.1",
1583 "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz",
1584 "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==",
1585 "license": "MIT",
1586 "dependencies": {
1587 "sodium-native": "^5.0.1"
1588 },
1589 "peerDependencies": {
1590 "sodium-javascript": "~0.8.0"
1591 },
1592 "peerDependenciesMeta": {
1593 "sodium-javascript": {
1594 "optional": true
1595 }
1596 }
1597 },
1598 "node_modules/sodium-universal/node_modules/sodium-native": {
1599 "version": "5.1.0",
1600 "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.1.0.tgz",
1601 "integrity": "sha512-3RxgyWyJlhTsABPnJVpCI5CoTDANZTqqFrEPqr+kjfnRaBihpVtMUE3yTF40ukdoB1APXeoBNKF3MzZAIHg39g==",
1602 "license": "MIT",
1603 "dependencies": {
1604 "bare-assert": "^1.2.0",
1605 "require-addon": "^1.1.0",
1606 "which-runtime": "^1.2.1"
1607 },
1608 "engines": {
1609 "bare": ">=1.16.0"
1610 }
1611 },
1612 "node_modules/stackframe": {
1613 "version": "1.3.4",
1614 "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
1615 "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
1616 "dev": true,
1617 "license": "MIT"
1618 },
1619 "node_modules/streamx": {
1620 "version": "2.25.0",
1621 "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz",
1622 "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==",
1623 "license": "MIT",
1624 "dependencies": {
1625 "events-universal": "^1.0.0",
1626 "fast-fifo": "^1.3.2",
1627 "text-decoder": "^1.1.0"
1628 }
1629 },
1630 "node_modules/sub-encoder": {
1631 "version": "2.1.3",
1632 "resolved": "https://registry.npmjs.org/sub-encoder/-/sub-encoder-2.1.3.tgz",
1633 "integrity": "sha512-Xxx04ygZo/1J3yHvaSA6VhDmiSaBQkw/PmO3YnnYFXle+tfOGToC6FcDpIfMztWZXJzuKG14b/57HMkiL58C6A==",
1634 "license": "Apache-2.0",
1635 "dependencies": {
1636 "b4a": "^1.6.0",
1637 "codecs": "^3.1.0"
1638 }
1639 },
1640 "node_modules/teex": {
1641 "version": "1.0.1",
1642 "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
1643 "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
1644 "license": "MIT",
1645 "dependencies": {
1646 "streamx": "^2.12.5"
1647 }
1648 },
1649 "node_modules/test-tmp": {
1650 "version": "1.4.0",
1651 "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz",
1652 "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==",
1653 "dev": true,
1654 "license": "MIT",
1655 "dependencies": {
1656 "bare-fs": "^4.0.1",
1657 "bare-os": "^3.3.0",
1658 "bare-path": "^3.0.0"
1659 }
1660 },
1661 "node_modules/text-decoder": {
1662 "version": "1.2.7",
1663 "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
1664 "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
1665 "license": "Apache-2.0",
1666 "dependencies": {
1667 "b4a": "^1.6.4"
1668 }
1669 },
1670 "node_modules/time-ordered-set": {
1671 "version": "2.0.1",
1672 "resolved": "https://registry.npmjs.org/time-ordered-set/-/time-ordered-set-2.0.1.tgz",
1673 "integrity": "sha512-VJEKmgSN2UiOLB8BpN8Sh2b9LGMHTP5OPrQRpnKjvOheOyzk0mufbjzjKTIG2gO4A+Y+vDJ+0TcLbpUmMLsg8A==",
1674 "license": "MIT"
1675 },
1676 "node_modules/timeout-refresh": {
1677 "version": "2.0.1",
1678 "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-2.0.1.tgz",
1679 "integrity": "sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==",
1680 "license": "MIT"
1681 },
1682 "node_modules/tiny-buffer-map": {
1683 "version": "1.1.1",
1684 "resolved": "https://registry.npmjs.org/tiny-buffer-map/-/tiny-buffer-map-1.1.1.tgz",
1685 "integrity": "sha512-C1eDw6ks9CmkDbWVCPHobuixPTkxGa7IDERlaVk98dv4tOUdz42o3haHBr0uhNxbj0gczBTVIyS2uQsu+1vc2Q==",
1686 "license": "MIT",
1687 "dependencies": {
1688 "b4a": "^1.6.0"
1689 }
1690 },
1691 "node_modules/tmatch": {
1692 "version": "5.0.0",
1693 "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz",
1694 "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==",
1695 "dev": true,
1696 "license": "ISC",
1697 "engines": {
1698 "node": ">=8"
1699 }
1700 },
1701 "node_modules/udx-native": {
1702 "version": "1.19.2",
1703 "resolved": "https://registry.npmjs.org/udx-native/-/udx-native-1.19.2.tgz",
1704 "integrity": "sha512-RNYh+UhfryCsF5hE2ZOuIqcZ+qdipXK3UsarwxWJwsUQZFE3ybwz0mPjwb5ev1PMBcjFahWiepS/q0wwL51c2g==",
1705 "license": "Apache-2.0",
1706 "dependencies": {
1707 "b4a": "^1.5.0",
1708 "bare-events": "^2.2.0",
1709 "require-addon": "^1.1.0",
1710 "streamx": "^2.22.0"
1711 },
1712 "engines": {
1713 "bare": ">=1.17.4"
1714 }
1715 },
1716 "node_modules/unordered-set": {
1717 "version": "2.0.1",
1718 "resolved": "https://registry.npmjs.org/unordered-set/-/unordered-set-2.0.1.tgz",
1719 "integrity": "sha512-eUmNTPzdx+q/WvOHW0bgGYLWvWHNT3PTKEQLg0MAQhc0AHASHVHoP/9YytYd4RBVariqno/mEUhVZN98CmD7bg==",
1720 "license": "MIT"
1721 },
1722 "node_modules/unslab": {
1723 "version": "1.3.0",
1724 "resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz",
1725 "integrity": "sha512-YATkfKAFj47kTzmiQrWXMyRvaVrHsW6MEALa4bm+FhiA2YG4oira+Z3DXN6LrYOYn2Y8eO94Lwl9DOHjs1FpoQ==",
1726 "license": "Apache-2.0",
1727 "dependencies": {
1728 "b4a": "^1.6.6"
1729 }
1730 },
1731 "node_modules/v8-to-istanbul": {
1732 "version": "9.3.0",
1733 "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
1734 "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
1735 "dev": true,
1736 "license": "ISC",
1737 "dependencies": {
1738 "@jridgewell/trace-mapping": "^0.3.12",
1739 "@types/istanbul-lib-coverage": "^2.0.1",
1740 "convert-source-map": "^2.0.0"
1741 },
1742 "engines": {
1743 "node": ">=10.12.0"
1744 }
1745 },
1746 "node_modules/varint": {
1747 "version": "5.0.0",
1748 "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.0.tgz",
1749 "integrity": "sha512-gC13b/bWrqQoKY2EmROCZ+AR0jitc6DnDGaQ6Ls9QpKmuSgJB1eQ7H3KETtQm7qSdMWMKCmsshyCmUwMLh3OAA==",
1750 "license": "MIT"
1751 },
1752 "node_modules/which-runtime": {
1753 "version": "1.3.2",
1754 "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz",
1755 "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==",
1756 "license": "Apache-2.0"
1757 },
1758 "node_modules/xache": {
1759 "version": "1.2.1",
1760 "resolved": "https://registry.npmjs.org/xache/-/xache-1.2.1.tgz",
1761 "integrity": "sha512-igRS6jPreJ54ABdzhh4mCDXcz+XMaWO2q1ABRV2yWYuk29jlp8VT7UBdCqNkX7rpYBbXsebVVKkwIuYZjyZNqA==",
1762 "license": "MIT"
1763 },
1764 "node_modules/z32": {
1765 "version": "1.1.0",
1766 "resolved": "https://registry.npmjs.org/z32/-/z32-1.1.0.tgz",
1767 "integrity": "sha512-1WUHy+VS6d0HPNspDxvLssBbeQjXMjSnpv0vH82vRAUfg847NmX3OXozp/hRP5jPhxBbrVzrgvAt+UsGNzRFQQ==",
1768 "license": "MIT",
1769 "dependencies": {
1770 "b4a": "^1.5.3"
1771 }
1772 }
1773 }
1774}
package.json +8 -1
……@@ -36,5 +36,12 @@
3636 "keet-identity-key": "github:holepunchto/keet-identity-key",
3737 "sodium-native": "^4.3.1"
3838 },
39 "license": "MIT"
39 "scripts": {
40 "test": "brittle test/*.test.js"
41 },
42 "license": "MIT",
43 "devDependencies": {
44 "brittle": "^3.19.1",
45 "random-access-memory": "^6.2.1"
46 }
4047}
test/anproto.test.js +201 -0
……@@ -0,0 +1,201 @@
1import test from 'brittle'
2import { ANProto } from '../lib/anproto.js'
3import b4a from 'b4a'
4
5test('gen() produces valid keypair', (t) => {
6 const kp = ANProto.gen()
7 t.is(kp.publicKey.length, 32, 'publicKey is 32 bytes')
8 t.is(kp.secretKey.length, 64, 'secretKey is 64 bytes')
9 t.is(typeof kp.keypair, 'string', 'keypair is a string')
10 t.is(kp.keypair.length, 132, 'keypair is 132 chars (44 pub + 88 sec)')
11})
12
13test('loadKeypair() round-trips with gen()', (t) => {
14 const original = ANProto.gen()
15 const loaded = ANProto.loadKeypair(original.keypair)
16 t.ok(b4a.equals(loaded.publicKey, original.publicKey), 'publicKey matches')
17 t.ok(b4a.equals(loaded.secretKey, original.secretKey), 'secretKey matches')
18})
19
20test('loadKeypair() rejects invalid lengths', (t) => {
21 t.exception(() => ANProto.loadKeypair('too-short'))
22 t.exception(() => ANProto.loadKeypair('a'.repeat(131)))
23 t.exception(() => ANProto.loadKeypair('a'.repeat(133)))
24})
25
26test('getPublicKey() extracts base64 pubkey', (t) => {
27 const kp = ANProto.gen()
28 const pubkey = ANProto.getPublicKey(kp.keypair)
29 t.is(pubkey.length, 44, '44-char base64')
30 t.is(pubkey, kp.keypair.substring(0, 44), 'matches first 44 chars')
31})
32
33// --- Challenge-Response Mode ---
34
35test('sign/verify challenge-response round-trip', (t) => {
36 const kp = ANProto.gen()
37 const challenge = 'test-challenge-12345'
38
39 const sig = ANProto.sign(challenge, kp)
40 t.is(typeof sig, 'string', 'signature is string')
41 t.is(b4a.from(sig, 'base64').length, 64, 'signature is 64 bytes (88 chars base64)')
42
43 const pubkeyB64 = b4a.toString(kp.publicKey, 'base64')
44 t.ok(ANProto.verify(challenge, sig, pubkeyB64), 'valid signature verifies')
45})
46
47test('verify rejects wrong challenge', (t) => {
48 const kp = ANProto.gen()
49 const sig = ANProto.sign('correct-challenge', kp)
50 const pubkeyB64 = b4a.toString(kp.publicKey, 'base64')
51 t.absent(ANProto.verify('wrong-challenge', sig, pubkeyB64), 'wrong challenge fails')
52})
53
54test('verify rejects wrong key', (t) => {
55 const kp1 = ANProto.gen()
56 const kp2 = ANProto.gen()
57 const challenge = 'test'
58 const sig = ANProto.sign(challenge, kp1)
59 const wrongKey = b4a.toString(kp2.publicKey, 'base64')
60 t.absent(ANProto.verify(challenge, sig, wrongKey), 'wrong key fails')
61})
62
63test('verify handles Buffer inputs', (t) => {
64 const kp = ANProto.gen()
65 const challenge = b4a.from('buffer-challenge')
66 const sig = ANProto.sign(challenge, kp)
67 t.ok(ANProto.verify(challenge, sig, kp.publicKey), 'Buffer inputs work')
68})
69
70// --- Content Signing Mode ---
71
72test('hash() produces deterministic SHA-256', (t) => {
73 const h1 = ANProto.hash('hello world')
74 const h2 = ANProto.hash('hello world')
75 t.is(h1, h2, 'same input = same hash')
76 t.is(h1.length, 44, '44-char base64 (32-byte hash)')
77
78 const h3 = ANProto.hash('different')
79 t.not(h1, h3, 'different input = different hash')
80})
81
82test('hash() accepts Buffer', (t) => {
83 const h1 = ANProto.hash('test')
84 const h2 = ANProto.hash(b4a.from('test'))
85 t.is(h1, h2, 'string and buffer produce same hash')
86})
87
88test('signContent/open round-trip', (t) => {
89 const kp = ANProto.gen()
90 const content = 'Hello, Wiredove!'
91 const contentHash = ANProto.hash(content)
92
93 const signed = ANProto.signContent(contentHash, kp)
94 t.is(typeof signed, 'string', 'signed is string')
95 t.ok(signed.length > 44, 'longer than just pubkey')
96 t.is(signed.substring(0, 44), b4a.toString(kp.publicKey, 'base64'), 'starts with pubkey')
97
98 const opened = ANProto.open(signed)
99 t.ok(opened.verified, 'verified is true')
100 t.is(opened.hash, contentHash, 'hash matches')
101 t.is(opened.publicKey, b4a.toString(kp.publicKey, 'base64'), 'pubkey matches')
102 t.ok(opened.timestamp > 0, 'timestamp is positive')
103 t.ok(opened.timestamp <= Date.now(), 'timestamp is not in the future')
104})
105
106test('open rejects tampered signature', (t) => {
107 const kp = ANProto.gen()
108 const signed = ANProto.signContent(ANProto.hash('test'), kp)
109
110 // Tamper with the signed data
111 const tampered = signed.substring(0, 44) + 'A' + signed.substring(45)
112 t.exception(() => ANProto.open(tampered), 'tampered message throws')
113})
114
115test('open rejects too-short messages', (t) => {
116 t.exception(() => ANProto.open('short'))
117 t.exception(() => ANProto.open('a'.repeat(43)))
118})
119
120// --- Deterministic Stringify ---
121
122test('deterministicStringify sorts keys', (t) => {
123 const a = ANProto.deterministicStringify({ b: 2, a: 1 })
124 const b = ANProto.deterministicStringify({ a: 1, b: 2 })
125 t.is(a, b, 'different key order = same output')
126 t.is(a, '{"a":1,"b":2}')
127})
128
129test('deterministicStringify handles nested objects', (t) => {
130 const result = ANProto.deterministicStringify({ z: { b: 2, a: 1 }, a: 'hello' })
131 t.is(result, '{"a":"hello","z":{"a":1,"b":2}}')
132})
133
134test('deterministicStringify handles arrays', (t) => {
135 const result = ANProto.deterministicStringify([3, 1, 2])
136 t.is(result, '[3,1,2]', 'arrays preserve order')
137})
138
139test('deterministicStringify handles null and primitives', (t) => {
140 t.is(ANProto.deterministicStringify(null), 'null')
141 t.is(ANProto.deterministicStringify(42), '42')
142 t.is(ANProto.deterministicStringify('hello'), '"hello"')
143 t.is(ANProto.deterministicStringify(true), 'true')
144})
145
146// --- High-Level Helpers ---
147
148test('createMessage/verifyMessage round-trip', (t) => {
149 const kp = ANProto.gen()
150 const content = { text: 'Hello world', author: 'alice' }
151
152 const msg = ANProto.createMessage(content, kp)
153 t.ok(msg.anproto, 'has anproto signature')
154 t.ok(msg.hash, 'has content hash')
155 t.is(msg.content, content, 'content preserved')
156
157 const verified = ANProto.verifyMessage(msg)
158 t.ok(verified.verified, 'verified is true')
159 t.is(verified.author, b4a.toString(kp.publicKey, 'base64'), 'author matches')
160 t.ok(verified.timestamp > 0, 'has timestamp')
161})
162
163test('verifyMessage rejects tampered content', (t) => {
164 const kp = ANProto.gen()
165 const msg = ANProto.createMessage({ text: 'original' }, kp)
166
167 msg.content = { text: 'tampered' }
168 t.exception(() => ANProto.verifyMessage(msg), 'tampered content throws')
169})
170
171test('verifyMessage rejects missing signature', (t) => {
172 t.exception(() => ANProto.verifyMessage({ content: 'no sig' }))
173 t.exception(() => ANProto.verifyMessage(null))
174})
175
176// --- Cross-Mode Compatibility ---
177
178test('same keypair works for both challenge-response and content signing', (t) => {
179 const kp = ANProto.gen()
180
181 // Challenge-response
182 const challenge = 'auth-challenge-xyz'
183 const sig = ANProto.sign(challenge, kp)
184 const pubkeyB64 = b4a.toString(kp.publicKey, 'base64')
185 t.ok(ANProto.verify(challenge, sig, pubkeyB64), 'challenge-response works')
186
187 // Content signing
188 const hash = ANProto.hash('post content')
189 const signed = ANProto.signContent(hash, kp)
190 const opened = ANProto.open(signed)
191 t.ok(opened.verified, 'content signing works')
192 t.is(opened.publicKey, pubkeyB64, 'same public key in both modes')
193})
194
195test('createMessage with string content', (t) => {
196 const kp = ANProto.gen()
197 const msg = ANProto.createMessage('plain string content', kp)
198 const verified = ANProto.verifyMessage(msg)
199 t.ok(verified.verified)
200 t.is(verified.content, 'plain string content')
201})
test/helpers.js +32 -0
……@@ -0,0 +1,32 @@
1import Corestore from 'corestore'
2import Hyperbee from 'hyperbee'
3import RAM from 'random-access-memory'
4import path from 'path'
5import os from 'os'
6import crypto from 'crypto'
7
8/**
9 * Create an in-memory Corestore + Hyperbee for testing
10 * @returns {{ store: Corestore, db: Hyperbee, cleanup: Function }}
11 */
12export async function createTestDb () {
13 const tmpDir = path.join(os.tmpdir(), 'hyper-identity-test-' + crypto.randomBytes(8).toString('hex'))
14 const store = new Corestore(tmpDir)
15 await store.ready()
16
17 const core = store.get({ name: 'test-db' })
18 const db = new Hyperbee(core, {
19 keyEncoding: 'utf-8',
20 valueEncoding: 'json'
21 })
22 await db.ready()
23
24 return {
25 store,
26 db,
27 async cleanup () {
28 await db.close()
29 await store.close()
30 }
31 }
32}
test/identity-manager.test.js +125 -0
……@@ -0,0 +1,125 @@
1import test from 'brittle'
2import { createTestDb } from './helpers.js'
3import { IdentityManager } from '../lib/identity-manager.js'
4
5test('ready() generates persistent keypair', async (t) => {
6 const { db, cleanup } = await createTestDb()
7 const mgr = new IdentityManager(db)
8 await mgr.ready()
9
10 t.ok(mgr.getPublicKey(), 'has public key')
11 t.is(mgr.getPublicKey().length, 32, 'public key is 32 bytes')
12 t.ok(mgr.getKeypair(), 'has keypair')
13 t.is(mgr.getKeypair().secretKey.length, 64, 'secret key is 64 bytes')
14
15 mgr.destroy()
16 await cleanup()
17})
18
19test('ready() loads existing keypair on re-init', async (t) => {
20 const { db, cleanup } = await createTestDb()
21
22 const mgr1 = new IdentityManager(db)
23 await mgr1.ready()
24 const pk1 = mgr1.getPublicKeyBase64()
25 mgr1.destroy()
26
27 const mgr2 = new IdentityManager(db)
28 await mgr2.ready()
29 const pk2 = mgr2.getPublicKeyBase64()
30 mgr2.destroy()
31
32 t.is(pk1, pk2, 'same keypair on re-init')
33
34 await cleanup()
35})
36
37test('getPublicKeyBase64 returns 44-char base64', async (t) => {
38 const { db, cleanup } = await createTestDb()
39 const mgr = new IdentityManager(db)
40 await mgr.ready()
41
42 const pk = mgr.getPublicKeyBase64()
43 t.is(typeof pk, 'string')
44 t.is(pk.length, 44, '44-char base64')
45
46 mgr.destroy()
47 await cleanup()
48})
49
50test('getKeypairString returns 132-char base64', async (t) => {
51 const { db, cleanup } = await createTestDb()
52 const mgr = new IdentityManager(db)
53 await mgr.ready()
54
55 const kp = mgr.getKeypairString()
56 t.is(typeof kp, 'string')
57 t.is(kp.length, 132, '132-char keypair')
58 t.is(kp.substring(0, 44), mgr.getPublicKeyBase64(), 'first 44 chars = pubkey')
59
60 mgr.destroy()
61 await cleanup()
62})
63
64test('getPublicKeyHex returns hex string', async (t) => {
65 const { db, cleanup } = await createTestDb()
66 const mgr = new IdentityManager(db)
67 await mgr.ready()
68
69 const hex = mgr.getPublicKeyHex()
70 t.is(typeof hex, 'string')
71 t.is(hex.length, 64, '64-char hex (32 bytes)')
72
73 mgr.destroy()
74 await cleanup()
75})
76
77test('sign and verify', async (t) => {
78 const { db, cleanup } = await createTestDb()
79 const mgr = new IdentityManager(db)
80 await mgr.ready()
81
82 const data = Buffer.from('test data to sign')
83 const sig = mgr.sign(data)
84 t.ok(sig, 'produces signature')
85 t.is(sig.length, 64, 'signature is 64 bytes')
86
87 t.ok(mgr.verify(data, sig), 'valid signature verifies')
88 t.absent(mgr.verify(Buffer.from('wrong data'), sig), 'wrong data fails')
89
90 mgr.destroy()
91 await cleanup()
92})
93
94test('setMnemonic and getMnemonic', async (t) => {
95 const { db, cleanup } = await createTestDb()
96 const mgr = new IdentityManager(db)
97 await mgr.ready()
98
99 t.absent(mgr.getMnemonic(), 'no mnemonic initially')
100
101 await mgr.setMnemonic('test word list here for mnemonic storage')
102 t.is(mgr.getMnemonic(), 'test word list here for mnemonic storage')
103
104 mgr.destroy()
105 await cleanup()
106})
107
108test('destroy zeros secret key', async (t) => {
109 const { db, cleanup } = await createTestDb()
110 const mgr = new IdentityManager(db)
111 await mgr.ready()
112
113 const secretKey = mgr.getKeypair().secretKey
114 mgr.destroy()
115
116 // After destroy, the buffer should be zeroed
117 let allZero = true
118 for (let i = 0; i < secretKey.length; i++) {
119 if (secretKey[i] !== 0) { allZero = false; break }
120 }
121 t.ok(allZero, 'secret key zeroed after destroy')
122 t.absent(mgr.getKeypair(), 'keypair is null after destroy')
123
124 await cleanup()
125})
test/identity-registry.test.js +271 -0
……@@ -0,0 +1,271 @@
1import test from 'brittle'
2import { createTestDb } from './helpers.js'
3import { IdentityRegistry } from '../lib/auth/identity-registry.js'
4
5async function createRegistry () {
6 const { store, db, cleanup } = await createTestDb()
7 const registry = new IdentityRegistry(store, { db })
8 await registry.ready()
9 return { registry, store, db, cleanup: async () => { await registry.close(); await cleanup() } }
10}
11
12test('createUser and getUser', async (t) => {
13 const { registry, cleanup } = await createRegistry()
14
15 const user = await registry.createUser({ name: 'Alice' })
16 t.ok(user.userId, 'has userId')
17 t.is(user.name, 'Alice')
18 t.alike(user.globalPermissions, ['read'])
19
20 const fetched = await registry.getUser(user.userId)
21 t.is(fetched.name, 'Alice')
22 t.is(fetched.userId, user.userId)
23
24 await cleanup()
25})
26
27test('registerIdentity and getIdentityByPublicKey', async (t) => {
28 const { registry, cleanup } = await createRegistry()
29
30 await registry.registerIdentity({
31 identityId: 'test-id',
32 method: 'web-identity',
33 publicKey: 'pubkey-abc123'
34 })
35
36 const identity = await registry.getIdentityByPublicKey('pubkey-abc123')
37 t.ok(identity, 'found by pubkey')
38 t.is(identity.identityId, 'test-id')
39 t.is(identity.method, 'web-identity')
40
41 await cleanup()
42})
43
44test('linkIdentityToUser and getUserByIdentity', async (t) => {
45 const { registry, cleanup } = await createRegistry()
46
47 const user = await registry.createUser({ name: 'Bob' })
48 await registry.registerIdentity({
49 identityId: 'bob-key',
50 method: 'web-identity',
51 publicKey: 'bob-pubkey'
52 })
53
54 await registry.linkIdentityToUser('bob-key', user.userId)
55
56 const found = await registry.getUserByIdentity('bob-key')
57 t.ok(found, 'found user by identity')
58 t.is(found.userId, user.userId)
59
60 // Direct-write db also updated
61 const identity = await registry.getIdentity('bob-key')
62 t.is(identity.userId, user.userId, 'identity record has userId')
63
64 await cleanup()
65})
66
67test('autoRegister — first user gets admin', async (t) => {
68 const { registry, cleanup } = await createRegistry()
69
70 const result = await registry.autoRegister({
71 identityId: 'first-key',
72 method: 'web-identity',
73 credentials: { publicKey: 'first-pubkey' }
74 })
75
76 t.ok(result.user, 'has user')
77 t.ok(result.identity, 'has identity')
78 t.ok(result.user.globalPermissions.includes('admin'), 'first user is admin')
79
80 await cleanup()
81})
82
83test('autoRegister — second user gets read-only', async (t) => {
84 const { registry, cleanup } = await createRegistry()
85
86 await registry.autoRegister({
87 identityId: 'first-key',
88 method: 'web-identity',
89 credentials: { publicKey: 'first-pubkey' }
90 })
91
92 const second = await registry.autoRegister({
93 identityId: 'second-key',
94 method: 'web-identity',
95 credentials: { publicKey: 'second-pubkey' }
96 })
97
98 t.absent(second.user.globalPermissions.includes('admin'), 'second user is not admin')
99 t.ok(second.user.globalPermissions.includes('read'), 'second user has read')
100
101 await cleanup()
102})
103
104test('validateUsername', async (t) => {
105 const { registry, cleanup } = await createRegistry()
106
107 t.ok(registry.validateUsername('alice').valid, 'alice is valid')
108 t.ok(registry.validateUsername('alice-bob').valid, 'alice-bob is valid')
109 t.absent(registry.validateUsername('AB').valid, 'uppercase rejected')
110 t.absent(registry.validateUsername('a').valid, 'too short rejected')
111 t.absent(registry.validateUsername('-abc').valid, 'leading dash rejected')
112 t.absent(registry.validateUsername('abc-').valid, 'trailing dash rejected')
113 t.absent(registry.validateUsername('a--b').valid, 'consecutive dashes rejected')
114 t.absent(registry.validateUsername('admin').valid, 'reserved name rejected')
115 t.absent(registry.validateUsername('api').valid, 'reserved name rejected')
116
117 await cleanup()
118})
119
120test('setUsername and getUserByUsername', async (t) => {
121 const { registry, cleanup } = await createRegistry()
122
123 const user = await registry.createUser({ name: 'Charlie' })
124 await registry.setUsername(user.userId, 'charlie')
125
126 const found = await registry.getUserByUsername('charlie')
127 t.ok(found, 'found by username')
128 t.is(found.userId, user.userId)
129 t.is(found.username, 'charlie')
130
131 await cleanup()
132})
133
134test('setUsername enforces uniqueness', async (t) => {
135 const { registry, cleanup } = await createRegistry()
136
137 const user1 = await registry.createUser({ name: 'A' })
138 const user2 = await registry.createUser({ name: 'B' })
139
140 await registry.setUsername(user1.userId, 'taken')
141
142 try {
143 await registry.setUsername(user2.userId, 'taken')
144 t.fail('should have thrown')
145 } catch (err) {
146 t.is(err.code, 'CONFLICT', 'throws CONFLICT')
147 }
148
149 await cleanup()
150})
151
152test('isUsernameAvailable', async (t) => {
153 const { registry, cleanup } = await createRegistry()
154
155 t.ok(await registry.isUsernameAvailable('fresh'), 'unused name is available')
156
157 const user = await registry.createUser({ name: 'Test' })
158 await registry.setUsername(user.userId, 'fresh')
159
160 t.absent(await registry.isUsernameAvailable('fresh'), 'taken name is not available')
161 t.absent(await registry.isUsernameAvailable('admin'), 'reserved name is not available')
162
163 await cleanup()
164})
165
166test('updateUserProfile', async (t) => {
167 const { registry, cleanup } = await createRegistry()
168
169 const user = await registry.createUser({ name: 'Old Name' })
170 const updated = await registry.updateUserProfile(user.userId, { name: 'New Name', theme: 'dark' })
171
172 t.is(updated.name, 'New Name')
173 t.is(updated.theme, 'dark')
174
175 await cleanup()
176})
177
178test('upgradePermissions', async (t) => {
179 const { registry, cleanup } = await createRegistry()
180
181 const user = await registry.createUser({ name: 'Test' })
182 await registry.upgradePermissions(user.userId, '*', ['read', 'write', 'admin'])
183
184 const fetched = await registry.getUser(user.userId)
185 t.ok(fetched.globalPermissions.includes('admin'), 'has admin after upgrade')
186
187 await cleanup()
188})
189
190test('revokeIdentity and isRevoked', async (t) => {
191 const { registry, cleanup } = await createRegistry()
192
193 await registry.registerIdentity({
194 identityId: 'revoke-me',
195 method: 'web-identity',
196 publicKey: 'revoke-pubkey'
197 })
198
199 t.absent(await registry.isRevoked('revoke-me'), 'not revoked initially')
200
201 await registry.revokeIdentity('revoke-me', { reason: 'compromised' })
202
203 t.ok(await registry.isRevoked('revoke-me'), 'revoked after revokeIdentity')
204
205 await cleanup()
206})
207
208test('restoreIdentity', async (t) => {
209 const { registry, cleanup } = await createRegistry()
210
211 await registry.registerIdentity({
212 identityId: 'restore-me',
213 method: 'web-identity',
214 publicKey: 'restore-pubkey'
215 })
216
217 await registry.revokeIdentity('restore-me')
218 t.ok(await registry.isRevoked('restore-me'), 'revoked')
219
220 await registry.restoreIdentity('restore-me')
221 t.absent(await registry.isRevoked('restore-me'), 'restored')
222
223 await cleanup()
224})
225
226test('mergeAccounts', async (t) => {
227 const { registry, cleanup } = await createRegistry()
228
229 const primary = await registry.createUser({ name: 'Primary', globalPermissions: ['read'] })
230 const secondary = await registry.createUser({ name: 'Secondary', globalPermissions: ['read', 'write'] })
231
232 await registry.registerIdentity({ identityId: 'sec-id', method: 'web-identity', publicKey: 'sec-pub' })
233 await registry.linkIdentityToUser('sec-id', secondary.userId)
234
235 await registry.mergeAccounts(primary.userId, secondary.userId)
236
237 const merged = await registry.getUser(secondary.userId)
238 t.is(merged.mergedInto, primary.userId, 'secondary marked as merged')
239
240 const kept = await registry.getUser(primary.userId)
241 t.ok(kept.globalPermissions.includes('write'), 'permissions merged')
242
243 await cleanup()
244})
245
246test('listUsers excludes merged accounts', async (t) => {
247 const { registry, cleanup } = await createRegistry()
248
249 const u1 = await registry.createUser({ name: 'Keep' })
250 const u2 = await registry.createUser({ name: 'Merge' })
251
252 await registry.mergeAccounts(u1.userId, u2.userId)
253
254 const users = await registry.listUsers()
255 t.is(users.length, 1, 'only active user listed')
256 t.is(users[0].name, 'Keep')
257
258 await cleanup()
259})
260
261test('deleteUser', async (t) => {
262 const { registry, cleanup } = await createRegistry()
263
264 const user = await registry.createUser({ name: 'Delete Me' })
265 t.ok(await registry.getUser(user.userId), 'exists before delete')
266
267 await registry.deleteUser(user.userId)
268 t.absent(await registry.getUser(user.userId), 'gone after delete')
269
270 await cleanup()
271})