Skip to content

[Feature Request] Add Axios Interceptor Configuration Support #404

@identiq

Description

@identiq

Problem Statement

Currently, there's no way to configure axios interceptors (request/response) through the jira.js client configuration. The internal AxiosInstance is private, making it impossible to:

  • Add custom request/response interceptors (e.g., for retry logic, rate limiting)
  • Implement automatic retry on rate limiting (HTTP 429)
  • Add custom authentication refresh logic
  • Implement request/response logging
  • Handle network errors with custom retry strategies

Users are forced to access the private instance property using type assertions, which is:

  • Not type-safe
  • Fragile (breaks if internal implementation changes)
  • Not officially supported
// Current workaround (not recommended)
const jira = new Version3Client({ host, authentication });
(jira as any).instance.interceptors.response.use(...); // ❌ Accessing private property

Proposed Solution

Add an interceptors configuration option to the Config interface that allows users to provide custom axios interceptors during client initialization.

TypeScript Interface

import type { AxiosResponse, InternalAxiosRequestConfig } from 'axios';

export interface Config {
  // ... existing config properties
  
  /**
   * Custom axios interceptors for request/response handling
   */
  interceptors?: {
    /**
     * Request interceptor - called before the request is sent
     */
    request?: {
      onFulfilled?: (
        config: InternalAxiosRequestConfig
      ) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>;
      onRejected?: (error: any) => any;
    };
    
    /**
     * Response interceptor - called after the response is received
     */
    response?: {
      onFulfilled?: (
        response: AxiosResponse
      ) => AxiosResponse | Promise<AxiosResponse>;
      onRejected?: (error: any) => any;
    };
  };
}

Implementation in BaseClient

export class BaseClient implements Client {
  private instance: AxiosInstance;

  constructor(protected readonly config: Config) {
    // ... existing validation and setup

    this.instance = axios.create({
      paramsSerializer: this.paramSerializer.bind(this),
      ...config.baseRequestConfig,
      baseURL: config.host,
      headers: this.removeUndefinedProperties({...}),
    });

    // Apply custom interceptors if provided
    if (config.interceptors?.request) {
      this.instance.interceptors.request.use(
        config.interceptors.request.onFulfilled,
        config.interceptors.request.onRejected,
      );
    }

    if (config.interceptors?.response) {
      this.instance.interceptors.response.use(
        config.interceptors.response.onFulfilled,
        config.interceptors.response.onRejected,
      );
    }
  }
  
  // ... rest of implementation
}

Use Cases

1. Automatic Retry on Rate Limiting (429)

import { Version3Client } from 'jira.js';

const jira = new Version3Client({
  host: 'https://your-domain.atlassian.net',
  authentication: {
    basic: { email: '...', apiToken: '...' }
  },
  interceptors: {
    response: {
      onRejected: async (error) => {
        const config = error.config;
        const retryCount = config.__retryCount || 0;
        
        // Retry on 429 with exponential backoff
        if (error.response?.status === 429 && retryCount < 3) {
          config.__retryCount = retryCount + 1;
          const delay = Math.min(1000 * Math.pow(2, retryCount), 30000);
          await new Promise(resolve => setTimeout(resolve, delay));
          return axios(config);
        }
        
        throw error;
      }
    }
  }
});

2. Request/Response Logging

const jira = new Version3Client({
  host: 'https://your-domain.atlassian.net',
  authentication: { basic: {...} },
  interceptors: {
    request: {
      onFulfilled: (config) => {
        console.log(`[JIRA] ${config.method?.toUpperCase()} ${config.url}`);
        return config;
      }
    },
    response: {
      onFulfilled: (response) => {
        console.log(`[JIRA] ${response.status} ${response.config.url}`);
        return response;
      }
    }
  }
});

3. Authentication Token Refresh

const jira = new Version3Client({
  host: 'https://your-domain.atlassian.net',
  authentication: { oauth2: { accessToken: '...' } },
  interceptors: {
    response: {
      onRejected: async (error) => {
        if (error.response?.status === 401) {
          // Refresh token and retry
          const newToken = await refreshAccessToken();
          error.config.headers.Authorization = `Bearer ${newToken}`;
          return axios(error.config);
        }
        throw error;
      }
    }
  }
});

Benefits

  1. Type-safe - Proper TypeScript support
  2. Officially supported - No reliance on private implementation details
  3. Flexible - Covers all interceptor use cases
  4. Backward compatible - Optional configuration, no breaking changes
  5. Standard pattern - Follows axios interceptor conventions
  6. Better DX - Cleaner, more maintainable code

Alternative Considered

Alternative 1: Accept pre-configured axios instance

interface Config {
  axiosInstance?: AxiosInstance;
}

Pros: Maximum flexibility
Cons: Users must manually configure all axios settings

Alternative 2: Expose instance via getter

public getInstance(): AxiosInstance {
  return this.instance;
}

Pros: Simple to implement
Cons: Exposes internal implementation, less control

Implementation Checklist

  • Add interceptors property to Config interface in src/config.ts
  • Update ConfigSchema to validate interceptors
  • Modify BaseClient constructor to apply interceptors
  • Add TypeScript types for interceptor functions
  • Add unit tests for interceptor functionality
  • Update documentation with examples
  • Add migration guide for users currently using workarounds

Additional Context

This feature would significantly improve jira.js usability for enterprise applications that need:

  • Robust error handling
  • Rate limit management
  • Request/response monitoring
  • Custom authentication flows

Many popular HTTP client wrappers (like Apollo Client, Axios directly) provide similar configuration options.


Would you like me to refine any part of this issue before you submit it?

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementDenotes a suggestion or request aimed at improving or adding new features to the project.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions