Skip to content

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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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