178 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			178 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import * as types from './types.js';
 | 
						|
import { CoreResponse } from './response.js';
 | 
						|
import { CoreRequest as AbstractCoreRequest } from '../core_base/request.js';
 | 
						|
 | 
						|
/**
 | 
						|
 * Fetch-based implementation of Core Request class
 | 
						|
 */
 | 
						|
export class CoreRequest extends AbstractCoreRequest<
 | 
						|
  types.ICoreRequestOptions,
 | 
						|
  CoreResponse
 | 
						|
> {
 | 
						|
  private timeoutId: ReturnType<typeof setTimeout> | null = null;
 | 
						|
  private abortController: AbortController | null = null;
 | 
						|
 | 
						|
  constructor(url: string, options: types.ICoreRequestOptions = {}) {
 | 
						|
    super(url, options);
 | 
						|
 | 
						|
    // Check for unsupported Node.js-specific options
 | 
						|
    if (options.agent || options.socketPath) {
 | 
						|
      throw new Error(
 | 
						|
        'Node.js specific options (agent, socketPath) are not supported in browser/fetch implementation',
 | 
						|
      );
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Build the full URL with query parameters
 | 
						|
   */
 | 
						|
  private buildUrl(): string {
 | 
						|
    if (
 | 
						|
      !this.options.queryParams ||
 | 
						|
      Object.keys(this.options.queryParams).length === 0
 | 
						|
    ) {
 | 
						|
      return this.url;
 | 
						|
    }
 | 
						|
 | 
						|
    const url = new URL(this.url);
 | 
						|
    Object.entries(this.options.queryParams).forEach(([key, value]) => {
 | 
						|
      url.searchParams.append(key, value);
 | 
						|
    });
 | 
						|
    return url.toString();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Convert our options to fetch RequestInit
 | 
						|
   */
 | 
						|
  private buildFetchOptions(): RequestInit {
 | 
						|
    const fetchOptions: RequestInit = {
 | 
						|
      method: this.options.method,
 | 
						|
      headers: this.options.headers,
 | 
						|
      credentials: this.options.credentials,
 | 
						|
      mode: this.options.mode,
 | 
						|
      cache: this.options.cache,
 | 
						|
      redirect: this.options.redirect,
 | 
						|
      referrer: this.options.referrer,
 | 
						|
      referrerPolicy: this.options.referrerPolicy,
 | 
						|
      integrity: this.options.integrity,
 | 
						|
      keepalive: this.options.keepAlive,
 | 
						|
      signal: this.options.signal,
 | 
						|
    };
 | 
						|
 | 
						|
    // Handle request body
 | 
						|
    if (this.options.requestBody !== undefined) {
 | 
						|
      if (
 | 
						|
        typeof this.options.requestBody === 'string' ||
 | 
						|
        this.options.requestBody instanceof ArrayBuffer ||
 | 
						|
        this.options.requestBody instanceof Uint8Array ||
 | 
						|
        this.options.requestBody instanceof FormData ||
 | 
						|
        this.options.requestBody instanceof URLSearchParams ||
 | 
						|
        this.options.requestBody instanceof ReadableStream ||
 | 
						|
        // Check for Buffer (Node.js polyfills in browser may provide this)
 | 
						|
        (typeof Buffer !== 'undefined' && this.options.requestBody instanceof Buffer)
 | 
						|
      ) {
 | 
						|
        fetchOptions.body = this.options.requestBody;
 | 
						|
        
 | 
						|
        // If streaming, we need to set duplex mode
 | 
						|
        if (this.options.requestBody instanceof ReadableStream) {
 | 
						|
          (fetchOptions as any).duplex = 'half';
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        // Convert objects to JSON
 | 
						|
        fetchOptions.body = JSON.stringify(this.options.requestBody);
 | 
						|
        // Set content-type if not already set
 | 
						|
        if (!fetchOptions.headers) {
 | 
						|
          fetchOptions.headers = { 'Content-Type': 'application/json' };
 | 
						|
        } else if (fetchOptions.headers instanceof Headers) {
 | 
						|
          if (!fetchOptions.headers.has('Content-Type')) {
 | 
						|
            fetchOptions.headers.set('Content-Type', 'application/json');
 | 
						|
          }
 | 
						|
        } else if (
 | 
						|
          typeof fetchOptions.headers === 'object' &&
 | 
						|
          !Array.isArray(fetchOptions.headers)
 | 
						|
        ) {
 | 
						|
          const headersObj = fetchOptions.headers as Record<string, string>;
 | 
						|
          if (!headersObj['Content-Type']) {
 | 
						|
            headersObj['Content-Type'] = 'application/json';
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // Handle timeout
 | 
						|
    if (this.options.timeout || this.options.hardDataCuttingTimeout) {
 | 
						|
      const timeout =
 | 
						|
        this.options.hardDataCuttingTimeout || this.options.timeout;
 | 
						|
      this.abortController = new AbortController();
 | 
						|
      this.timeoutId = setTimeout(() => {
 | 
						|
        if (this.abortController) {
 | 
						|
          this.abortController.abort();
 | 
						|
        }
 | 
						|
      }, timeout);
 | 
						|
      fetchOptions.signal = this.abortController.signal;
 | 
						|
    }
 | 
						|
 | 
						|
    return fetchOptions;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Fire the request and return a CoreResponse
 | 
						|
   */
 | 
						|
  async fire(): Promise<CoreResponse> {
 | 
						|
    const response = await this.fireCore();
 | 
						|
    return new CoreResponse(response);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Fire the request and return the raw Response
 | 
						|
   */
 | 
						|
  async fireCore(): Promise<Response> {
 | 
						|
    const url = this.buildUrl();
 | 
						|
    const options = this.buildFetchOptions();
 | 
						|
 | 
						|
    try {
 | 
						|
      const response = await fetch(url, options);
 | 
						|
      // Clear timeout on successful response
 | 
						|
      this.clearTimeout();
 | 
						|
      return response;
 | 
						|
    } catch (error) {
 | 
						|
      // Clear timeout on error
 | 
						|
      this.clearTimeout();
 | 
						|
      if (error.name === 'AbortError') {
 | 
						|
        throw new Error('Request timed out');
 | 
						|
      }
 | 
						|
      throw error;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Clear the timeout and abort controller
 | 
						|
   */
 | 
						|
  private clearTimeout(): void {
 | 
						|
    if (this.timeoutId) {
 | 
						|
      clearTimeout(this.timeoutId);
 | 
						|
      this.timeoutId = null;
 | 
						|
    }
 | 
						|
    if (this.abortController) {
 | 
						|
      this.abortController = null;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Static factory method to create and fire a request
 | 
						|
   */
 | 
						|
  static async create(
 | 
						|
    url: string,
 | 
						|
    options: types.ICoreRequestOptions = {},
 | 
						|
  ): Promise<CoreResponse> {
 | 
						|
    const request = new CoreRequest(url, options);
 | 
						|
    return request.fire();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Convenience exports for backward compatibility
 | 
						|
 */
 | 
						|
export const isUnixSocket = CoreRequest.isUnixSocket;
 | 
						|
export const parseUnixSocketUrl = CoreRequest.parseUnixSocketUrl;
 |