Skip to content

[Feature] Add in-memory asset data cache to eliminate redundant API calls on repeated modal opens #78

@numbers-official

Description

@numbers-official

Summary

ModalManager.updateModal() fires three independent API requests every time a user opens the modal for a given nid, even if the same asset was fetched moments ago. Adding a simple in-memory cache with a configurable TTL would eliminate redundant network traffic and significantly improve modal responsiveness.

Current Behavior

In src/modal/modal-manager.ts (lines 47-62), every modal open with a changed nid triggers:

if (nidChanged) {
  fetchAsset(options.nid).then((assetData) => {
    this.updateModalAsset(assetData, true);
  });
  hasNftProduct(options.nid).then((hasNftProduct) =>
    this.updateModalAsset({ hasNftProduct }, false)
  );
  fetchAssetMetadata(options.nid).then((metadata) => {
    if (metadata) {
      this.updateModalAsset({
        hasC2pa: metadata.hasC2pa,
        showcaseLink: metadata.showcaseLink,
      }, false);
    }
  });
}

The nidChanged check only compares against the currently displayed nid. If a user opens asset A, closes the modal, then opens asset A again, all three requests fire again.

Proposed Solution

Add a Map-based cache in asset-service.ts with a configurable TTL (e.g., 5 minutes):

// src/asset/asset-cache.ts
interface CacheEntry<T> {
  data: T;
  timestamp: number;
}

class AssetCache {
  private cache = new Map<string, CacheEntry<unknown>>();
  private ttlMs: number;

  constructor(ttlMs = 5 * 60 * 1000) {
    this.ttlMs = ttlMs;
  }

  get<T>(key: string): T | undefined {
    const entry = this.cache.get(key);
    if (!entry) return undefined;
    if (Date.now() - entry.timestamp > this.ttlMs) {
      this.cache.delete(key);
      return undefined;
    }
    return entry.data as T;
  }

  set<T>(key: string, data: T): void {
    this.cache.set(key, { data, timestamp: Date.now() });
  }

  invalidate(key?: string): void {
    key ? this.cache.delete(key) : this.cache.clear();
  }
}

export const assetCache = new AssetCache();

Then wrap the fetch functions:

export async function fetchAsset(nid: string): Promise<AssetData> {
  const cacheKey = `asset:${nid}`;
  const cached = assetCache.get<AssetData>(cacheKey);
  if (cached) return cached;

  // ... existing fetch logic ...
  assetCache.set(cacheKey, result);
  return result;
}

Expected Impact

  • Performance: Eliminates ~200-500ms latency on repeat modal opens for the same asset
  • Network efficiency: Reduces API calls by an estimated 40-60% in typical browse-and-return patterns
  • Server load: Proportional reduction in backend API requests across all embedded sites
  • User experience: Near-instant modal display when revisiting previously viewed assets
  • Code change scope: ~50-60 lines of new code, minimal changes to existing functions

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions