87fde102f9f1557efa2bd0be3826f459e0cd6ddca6aa92249795c4f4ee13bdd4.json raw

   1  {"ast":null,"code":"import _asyncToGenerator from \"/home/mleku/src/orly.dev/next/signer/node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js\";\nimport { inject } from '@angular/core';\nimport { SimplePool } from 'nostr-tools/pool';\nimport { FALLBACK_PROFILE_RELAYS } from '../../constants/fallback-relays';\nimport { LoggerService } from '../logger/logger.service';\nimport * as i0 from \"@angular/core\";\nconst CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours\nconst FETCH_TIMEOUT_MS = 10000; // 10 seconds\nconst STORAGE_KEY = 'profileMetadataCache';\nexport let ProfileMetadataService = /*#__PURE__*/(() => {\n  class ProfileMetadataService {\n    #logger = inject(LoggerService);\n    #cache = {};\n    #pool = null;\n    #fetchPromises = new Map();\n    #initialized = false;\n    #initPromise = null;\n    /**\n     * Initialize the service by loading cache from session storage\n     */\n    initialize() {\n      var _this = this;\n      return _asyncToGenerator(function* () {\n        if (_this.#initialized) {\n          return;\n        }\n        if (_this.#initPromise) {\n          return _this.#initPromise;\n        }\n        _this.#initPromise = _this.#loadCacheFromStorage();\n        yield _this.#initPromise;\n        _this.#initialized = true;\n      })();\n    }\n    /**\n     * Load cache from browser session storage\n     */\n    #loadCacheFromStorage() {\n      var _this2 = this;\n      return _asyncToGenerator(function* () {\n        try {\n          // Use chrome API (works in both Chrome and Firefox with polyfill)\n          if (typeof chrome !== 'undefined' && chrome.storage?.session) {\n            const result = yield chrome.storage.session.get(STORAGE_KEY);\n            if (result[STORAGE_KEY]) {\n              _this2.#cache = result[STORAGE_KEY];\n              // Clean up stale entries\n              _this2.#pruneStaleCache();\n            }\n          }\n        } catch (error) {\n          const errorMsg = error instanceof Error ? error.message : 'Unknown error';\n          _this2.#logger.logStorageError('load profile cache', errorMsg);\n        }\n      })();\n    }\n    /**\n     * Save cache to browser session storage\n     */\n    #saveCacheToStorage() {\n      var _this3 = this;\n      return _asyncToGenerator(function* () {\n        try {\n          if (typeof chrome !== 'undefined' && chrome.storage?.session) {\n            yield chrome.storage.session.set({\n              [STORAGE_KEY]: _this3.#cache\n            });\n          }\n        } catch (error) {\n          const errorMsg = error instanceof Error ? error.message : 'Unknown error';\n          _this3.#logger.logStorageError('save profile cache', errorMsg);\n        }\n      })();\n    }\n    /**\n     * Remove stale entries from cache\n     */\n    #pruneStaleCache() {\n      const now = Date.now();\n      for (const pubkey of Object.keys(this.#cache)) {\n        if (now - this.#cache[pubkey].fetchedAt > CACHE_TTL_MS) {\n          delete this.#cache[pubkey];\n        }\n      }\n    }\n    /**\n     * Get the SimplePool instance, creating it if necessary\n     */\n    #getPool() {\n      if (!this.#pool) {\n        this.#pool = new SimplePool();\n      }\n      return this.#pool;\n    }\n    /**\n     * Get cached profile metadata for a pubkey\n     */\n    getCachedProfile(pubkey) {\n      const cached = this.#cache[pubkey];\n      if (!cached) {\n        return null;\n      }\n      // Check if cache is still valid\n      if (Date.now() - cached.fetchedAt > CACHE_TTL_MS) {\n        delete this.#cache[pubkey];\n        return null;\n      }\n      return cached;\n    }\n    /**\n     * Fetch profile metadata for a single pubkey\n     */\n    fetchProfile(pubkey) {\n      var _this4 = this;\n      return _asyncToGenerator(function* () {\n        // Ensure initialized\n        yield _this4.initialize();\n        // Check cache first\n        const cached = _this4.getCachedProfile(pubkey);\n        if (cached) {\n          return cached;\n        }\n        // Check if already fetching\n        const existingPromise = _this4.#fetchPromises.get(pubkey);\n        if (existingPromise) {\n          return existingPromise;\n        }\n        // Start new fetch\n        const fetchPromise = _this4.#doFetchProfile(pubkey);\n        _this4.#fetchPromises.set(pubkey, fetchPromise);\n        try {\n          const result = yield fetchPromise;\n          return result;\n        } finally {\n          _this4.#fetchPromises.delete(pubkey);\n        }\n      })();\n    }\n    /**\n     * Fetch profiles for multiple pubkeys in parallel\n     */\n    fetchProfiles(pubkeys) {\n      var _this5 = this;\n      return _asyncToGenerator(function* () {\n        // Ensure initialized\n        yield _this5.initialize();\n        const results = new Map();\n        // Filter out pubkeys we already have cached\n        const uncachedPubkeys = [];\n        for (const pubkey of pubkeys) {\n          const cached = _this5.getCachedProfile(pubkey);\n          if (cached) {\n            results.set(pubkey, cached);\n          } else {\n            uncachedPubkeys.push(pubkey);\n          }\n        }\n        if (uncachedPubkeys.length === 0) {\n          return results;\n        }\n        // Fetch all uncached profiles\n        const pool = _this5.#getPool();\n        try {\n          const events = yield _this5.#queryWithTimeout(pool, FALLBACK_PROFILE_RELAYS, [{\n            kinds: [0],\n            authors: uncachedPubkeys\n          }], FETCH_TIMEOUT_MS);\n          // Process events - keep only the most recent event per pubkey\n          const latestEvents = new Map();\n          for (const event of events) {\n            const existing = latestEvents.get(event.pubkey);\n            if (!existing || event.created_at > existing.created_at) {\n              latestEvents.set(event.pubkey, {\n                created_at: event.created_at,\n                content: event.content\n              });\n            }\n          }\n          // Parse and cache the profiles\n          for (const [pubkey, eventData] of latestEvents) {\n            try {\n              const content = JSON.parse(eventData.content);\n              const profile = {\n                pubkey,\n                name: content.name,\n                display_name: content.display_name,\n                displayName: content.displayName,\n                picture: content.picture,\n                banner: content.banner,\n                about: content.about,\n                website: content.website,\n                nip05: content.nip05,\n                lud06: content.lud06,\n                lud16: content.lud16,\n                fetchedAt: Date.now()\n              };\n              _this5.#cache[pubkey] = profile;\n              results.set(pubkey, profile);\n            } catch {\n              _this5.#logger.logProfileParseError(pubkey);\n              results.set(pubkey, null);\n            }\n          }\n          // Set null for pubkeys we didn't find\n          for (const pubkey of uncachedPubkeys) {\n            if (!results.has(pubkey)) {\n              results.set(pubkey, null);\n            }\n          }\n          // Save updated cache to storage\n          yield _this5.#saveCacheToStorage();\n        } catch (error) {\n          const errorMsg = error instanceof Error ? error.message : 'Unknown error';\n          _this5.#logger.logProfileFetchError('multiple', errorMsg);\n          // Set null for all unfetched pubkeys on error\n          for (const pubkey of uncachedPubkeys) {\n            if (!results.has(pubkey)) {\n              results.set(pubkey, null);\n            }\n          }\n        }\n        return results;\n      })();\n    }\n    /**\n     * Internal method to fetch a single profile\n     */\n    #doFetchProfile(pubkey) {\n      var _this6 = this;\n      return _asyncToGenerator(function* () {\n        const pool = _this6.#getPool();\n        try {\n          const events = yield _this6.#queryWithTimeout(pool, FALLBACK_PROFILE_RELAYS, [{\n            kinds: [0],\n            authors: [pubkey]\n          }], FETCH_TIMEOUT_MS);\n          if (events.length === 0) {\n            return null;\n          }\n          // Get the most recent event\n          const latestEvent = events.reduce((latest, event) => event.created_at > latest.created_at ? event : latest);\n          try {\n            const content = JSON.parse(latestEvent.content);\n            const profile = {\n              pubkey,\n              name: content.name,\n              display_name: content.display_name,\n              displayName: content.displayName,\n              picture: content.picture,\n              banner: content.banner,\n              about: content.about,\n              website: content.website,\n              nip05: content.nip05,\n              lud06: content.lud06,\n              lud16: content.lud16,\n              fetchedAt: Date.now()\n            };\n            _this6.#cache[pubkey] = profile;\n            // Save updated cache to storage\n            yield _this6.#saveCacheToStorage();\n            return profile;\n          } catch {\n            _this6.#logger.logProfileParseError(pubkey);\n            return null;\n          }\n        } catch (error) {\n          const errorMsg = error instanceof Error ? error.message : 'Unknown error';\n          _this6.#logger.logProfileFetchError(pubkey, errorMsg);\n          return null;\n        }\n      })();\n    }\n    /**\n     * Query relays with a timeout\n     */\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    #queryWithTimeout(pool, relays, filters, timeoutMs) {\n      return _asyncToGenerator(function* () {\n        return new Promise(resolve => {\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          const events = [];\n          let settled = false;\n          const timeout = setTimeout(() => {\n            if (!settled) {\n              settled = true;\n              resolve(events);\n            }\n          }, timeoutMs);\n          const sub = pool.subscribeMany(relays, filters, {\n            onevent(event) {\n              events.push(event);\n            },\n            oneose() {\n              if (!settled) {\n                settled = true;\n                clearTimeout(timeout);\n                sub.close();\n                resolve(events);\n              }\n            }\n          });\n        });\n      })();\n    }\n    /**\n     * Clear the cache\n     */\n    clearCache() {\n      var _this7 = this;\n      return _asyncToGenerator(function* () {\n        _this7.#cache = {};\n        yield _this7.#saveCacheToStorage();\n      })();\n    }\n    /**\n     * Clear cache for a specific pubkey\n     */\n    clearCacheForPubkey(pubkey) {\n      var _this8 = this;\n      return _asyncToGenerator(function* () {\n        delete _this8.#cache[pubkey];\n        yield _this8.#saveCacheToStorage();\n      })();\n    }\n    /**\n     * Get the display name for a profile (prioritizes display_name over name)\n     */\n    getDisplayName(profile) {\n      if (!profile) return undefined;\n      return profile.display_name || profile.displayName || profile.name;\n    }\n    /**\n     * Get the username for a profile (prioritizes name over display_name)\n     */\n    getUsername(profile) {\n      if (!profile) return undefined;\n      return profile.name || profile.display_name || profile.displayName;\n    }\n    static ɵfac = function ProfileMetadataService_Factory(__ngFactoryType__) {\n      return new (__ngFactoryType__ || ProfileMetadataService)();\n    };\n    static ɵprov = /*@__PURE__*/i0.ɵɵdefineInjectable({\n      token: ProfileMetadataService,\n      factory: ProfileMetadataService.ɵfac,\n      providedIn: 'root'\n    });\n  }\n  return ProfileMetadataService;\n})();","map":null,"metadata":{},"sourceType":"module","externalDependencies":[]}