Skip to content

[Feature] Add request cancellation to prevent stale data race conditions in ModalManager #74

@numbers-official

Description

@numbers-official

Description

The ModalManager.updateModal() method in src/modal/modal-manager.ts (lines 33-62) fires three parallel API requests without any mechanism to cancel or discard stale responses when the user opens a different asset.

Current Behavior

// modal-manager.ts lines 47-61
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);
    }
  });
}

Problem

When a user rapidly clicks between multiple capture-eye elements:

  1. User clicks Asset A → 3 fetches start for NID-A
  2. User clicks Asset B before NID-A responses arrive → modal clears, 3 new fetches start for NID-B
  3. NID-A responses arrive → updateModalAsset() writes NID-A data into the modal now showing NID-B
  4. NID-B responses arrive later → partial overwrite, resulting in mixed data

This creates a data integrity issue where the modal displays a mix of data from two different assets. In a provenance verification widget, showing wrong creator/ownership data is a significant trust concern.

Suggested Implementation

Use AbortController to cancel in-flight requests when a new asset is opened:

private currentAbortController: AbortController | null = null;

async updateModal(options: ModalOptions, delay = 150): Promise<void> {
  if (this.currentAbortController) {
    this.currentAbortController.abort();
  }
  this.currentAbortController = new AbortController();
  const signal = this.currentAbortController.signal;

  // ... existing modal setup ...

  if (nidChanged) {
    const currentNid = options.nid;
    fetchAsset(currentNid, { signal }).then((assetData) => {
      if (this.nid === currentNid) {
        this.updateModalAsset(assetData, true);
      }
    }).catch(err => {
      if (err.name !== 'AbortError') console.error('Fetch error:', err);
    });
    // same pattern for hasNftProduct and fetchAssetMetadata
  }
}

Expected Impact

Files to Modify

  1. src/modal/modal-manager.ts — Add AbortController lifecycle
  2. src/asset/asset-service.ts — Accept optional AbortSignal in fetch functions
  3. src/modal/interaction-tracker.ts — Accept optional AbortSignal for event tracking

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