jira.js icon indicating copy to clipboard operation
jira.js copied to clipboard

[Feature Request] Add Axios Interceptor Configuration Support

Open identiq opened this issue 2 months ago • 1 comments

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?

identiq avatar Oct 21 '25 11:10 identiq

Hi, I agree — this is a well-thought-out and very useful proposal. Adding support for interceptors will make the client much more flexible and easier to integrate in real-world projects. It’s definitely something that should be implemented

MrRefactoring avatar Oct 21 '25 17:10 MrRefactoring