{"version":3,"file":"client-config-api-service.mjs","sourceRoot":"","sources":["../../src/client-config-api-service/client-config-api-service.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n  circuitBreaker,\n  ConsecutiveBreaker,\n  ExponentialBackoff,\n  handleAll,\n  type IPolicy,\n  retry,\n  wrap,\n  CircuitState,\n} from 'cockatiel';\n\nimport { BASE_URL } from '../constants';\nimport type {\n  FeatureFlags,\n  ClientType,\n  DistributionType,\n  EnvironmentType,\n  ServiceResponse,\n  ApiDataResponse,\n} from '../remote-feature-flag-controller-types';\n\nconst DEFAULT_FETCH_RETRIES = 3;\n// Each update attempt will result (1 + retries) calls if the server is down\nconst DEFAULT_MAX_CONSECUTIVE_FAILURES = (1 + DEFAULT_FETCH_RETRIES) * 3;\n\nexport const DEFAULT_DEGRADED_THRESHOLD = 5000;\n\n/**\n * This service is responsible for fetching feature flags from the ClientConfig API.\n */\nexport class ClientConfigApiService {\n  #fetch: typeof fetch;\n\n  #policy: IPolicy;\n\n  #client: ClientType;\n\n  #distribution: DistributionType;\n\n  #environment: EnvironmentType;\n\n  /**\n   * Constructs a new ClientConfigApiService object.\n   *\n   * @param args - The arguments.\n   * @param args.fetch - A function that can be used to make an HTTP request.\n   * @param args.retries - Number of retry attempts for each fetch request.\n   * @param args.maximumConsecutiveFailures - The maximum number of consecutive failures\n   * allowed before breaking the circuit and pausing further fetch attempts.\n   * @param args.circuitBreakDuration - The duration for which the circuit remains open after\n   * too many consecutive failures.\n   * @param args.onBreak - Callback invoked when the circuit breaks.\n   * @param args.onDegraded - Callback invoked when the service is degraded (requests resolving too slowly).\n   * @param args.config - The configuration object, includes client, distribution, and environment.\n   * @param args.config.client - The client type (e.g., 'extension', 'mobile').\n   * @param args.config.distribution - The distribution type (e.g., 'main', 'flask').\n   * @param args.config.environment - The environment type (e.g., 'prod', 'rc', 'dev').\n   */\n  constructor({\n    fetch: fetchFunction,\n    retries = DEFAULT_FETCH_RETRIES,\n    maximumConsecutiveFailures = DEFAULT_MAX_CONSECUTIVE_FAILURES,\n    circuitBreakDuration = 30 * 60 * 1000,\n    onBreak,\n    onDegraded,\n    config,\n  }: {\n    fetch: typeof fetch;\n    retries?: number;\n    maximumConsecutiveFailures?: number;\n    circuitBreakDuration?: number;\n    onBreak?: () => void;\n    onDegraded?: () => void;\n    config: {\n      client: ClientType;\n      distribution: DistributionType;\n      environment: EnvironmentType;\n    };\n  }) {\n    this.#fetch = fetchFunction;\n    this.#client = config.client;\n    this.#distribution = config.distribution;\n    this.#environment = config.environment;\n\n    const retryPolicy = retry(handleAll, {\n      maxAttempts: retries,\n      backoff: new ExponentialBackoff(),\n    });\n\n    const circuitBreakerPolicy = circuitBreaker(handleAll, {\n      halfOpenAfter: circuitBreakDuration,\n      breaker: new ConsecutiveBreaker(maximumConsecutiveFailures),\n    });\n\n    if (onBreak) {\n      circuitBreakerPolicy.onBreak(onBreak);\n    }\n\n    if (onDegraded) {\n      retryPolicy.onGiveUp(() => {\n        if (circuitBreakerPolicy.state === CircuitState.Closed) {\n          onDegraded();\n        }\n      });\n\n      retryPolicy.onSuccess(({ duration }) => {\n        if (\n          circuitBreakerPolicy.state === CircuitState.Closed &&\n          duration > DEFAULT_DEGRADED_THRESHOLD // Default degraded threshold\n        ) {\n          onDegraded();\n        }\n      });\n    }\n\n    this.#policy = wrap(retryPolicy, circuitBreakerPolicy);\n  }\n\n  /**\n   * Fetches feature flags from the API with specific client, distribution, and environment parameters.\n   * Provides structured error handling, including fallback to cached data if available.\n   * @returns An object of feature flags and their boolean values or a structured error object.\n   */\n  public async fetchRemoteFeatureFlags(): Promise<ServiceResponse> {\n    const url = `${BASE_URL}/flags?client=${this.#client}&distribution=${\n      this.#distribution\n    }&environment=${this.#environment}`;\n\n    const response = await this.#policy.execute(() =>\n      this.#fetch(url, { cache: 'no-cache' }),\n    );\n\n    if (!response.ok) {\n      throw new Error('Failed to fetch remote feature flags');\n    }\n\n    const data = await response.json();\n\n    if (!Array.isArray(data)) {\n      throw new Error('Feature flags api did not return an array');\n    }\n\n    const remoteFeatureFlags = this.flattenFeatureFlags(data);\n\n    return {\n      remoteFeatureFlags,\n      cacheTimestamp: Date.now(),\n    };\n  }\n\n  /**\n   * Flattens an array of feature flag objects into a single feature flags object.\n   * @param responseData - Array of objects containing feature flag key-value pairs\n   * @returns A single object containing all feature flags merged together\n   * @example\n   * // Input: [{ flag1: true }, { flag2: [] }]\n   * // Output: { flag1: true, flag2: [] }\n   */\n  private flattenFeatureFlags(responseData: ApiDataResponse): FeatureFlags {\n    return responseData.reduce((acc, curr) => {\n      return { ...acc, ...curr };\n    }, {});\n  }\n}\n"]}