{"version":3,"file":"AnalyticsDataRegulationService.mjs","sourceRoot":"","sources":["../src/AnalyticsDataRegulationService.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n  CreateServicePolicyOptions,\n  ServicePolicy,\n} from '@metamask/controller-utils';\nimport { createServicePolicy, HttpError } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport type { IDisposable } from 'cockatiel';\n\nimport type { AnalyticsDataRegulationServiceMethodActions } from './AnalyticsDataRegulationService-method-action-types';\nimport { DATA_DELETE_RESPONSE_STATUSES, DATA_DELETE_STATUSES } from './types';\nimport type { DataDeleteStatus } from './types';\n\n/**\n * Segment API regulation type for DELETE_ONLY operations.\n */\nconst SEGMENT_REGULATION_TYPE_DELETE_ONLY = 'DELETE_ONLY';\n\n/**\n * Segment API subject type for user ID operations.\n */\nconst SEGMENT_SUBJECT_TYPE_USER_ID = 'USER_ID';\n\n/**\n * Segment API Content-Type header value.\n */\nconst SEGMENT_CONTENT_TYPE = 'application/vnd.segment.v1+json';\n\n// === GENERAL ===\n\n/**\n * The name of the {@link AnalyticsDataRegulationService}, used to namespace the\n * service's actions and events.\n */\nexport const serviceName = 'AnalyticsDataRegulationService';\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = [\n  'createDataDeletionTask',\n  'checkDataDeleteStatus',\n] as const;\n\n/**\n * Actions that {@link AnalyticsDataRegulationService} exposes to other consumers.\n */\nexport type AnalyticsDataRegulationServiceActions =\n  AnalyticsDataRegulationServiceMethodActions;\n\n/**\n * Actions from other messengers that {@link AnalyticsDataRegulationServiceMessenger} calls.\n */\ntype AllowedActions = never;\n\n/**\n * Events that {@link AnalyticsDataRegulationService} exposes to other consumers.\n */\nexport type AnalyticsDataRegulationServiceEvents = never;\n\n/**\n * Events from other messengers that {@link AnalyticsDataRegulationService} subscribes to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger which is restricted to actions and events accessed by\n * {@link AnalyticsDataRegulationService}.\n */\nexport type AnalyticsDataRegulationServiceMessenger = Messenger<\n  typeof serviceName,\n  AnalyticsDataRegulationServiceActions | AllowedActions,\n  AnalyticsDataRegulationServiceEvents | AllowedEvents\n>;\n\n// === SERVICE DEFINITION ===\n\n/**\n * Response structure from Segment API for creating a regulation.\n */\ntype CreateRegulationResponse = {\n  data: {\n    data: {\n      regulateId: string;\n    };\n  };\n};\n\n/**\n * Response structure from Segment API for getting regulation status.\n */\ntype GetRegulationStatusResponse = {\n  data: {\n    data: {\n      regulation: {\n        overallStatus: string;\n      };\n    };\n  };\n};\n\n/**\n * Options for constructing {@link AnalyticsDataRegulationService}.\n */\nexport type AnalyticsDataRegulationServiceOptions = {\n  /**\n   * The messenger suited for this service.\n   */\n  messenger: AnalyticsDataRegulationServiceMessenger;\n\n  /**\n   * A function that can be used to make an HTTP request.\n   */\n  fetch: typeof fetch;\n\n  /**\n   * Segment API source ID (required for creating regulations).\n   */\n  segmentSourceId: string;\n\n  /**\n   * Base URL for the proxy endpoint that communicates with Segment's Regulations API.\n   * This is a proxy endpoint (not Segment API directly) that forwards requests to Segment's\n   * Regulations API and adds authentication tokens. The endpoint URL varies by environment\n   * (e.g., development, staging, production) and should be configured accordingly.\n   * Example: 'https://proxy.example.com/v1beta'\n   */\n  segmentRegulationsEndpoint: string;\n\n  /**\n   * Options to pass to `createServicePolicy`, which is used to wrap each request.\n   */\n  policyOptions?: CreateServicePolicyOptions;\n};\n\n/**\n * Type guard to check if a value is a valid DataDeleteStatus.\n *\n * @param status - The value to check.\n * @returns True if the value is a valid DataDeleteStatus.\n */\nfunction isDataDeleteStatus(status: unknown): status is DataDeleteStatus {\n  const dataDeleteStatuses: string[] = Object.values(DATA_DELETE_STATUSES);\n  return dataDeleteStatuses.includes(status as string);\n}\n\n/**\n * This service object is responsible for making requests to the Segment Regulations API\n * via a proxy endpoint for GDPR/CCPA data deletion functionality.\n *\n * @example\n *\n * ```ts\n * import { Messenger } from '@metamask/messenger';\n * import type {\n *   AnalyticsDataRegulationServiceActions,\n *   AnalyticsDataRegulationServiceEvents,\n * } from '@metamask/analytics-data-regulation-controller';\n *\n * const rootMessenger = new Messenger<\n *   'Root',\n *   AnalyticsDataRegulationServiceActions,\n *   AnalyticsDataRegulationServiceEvents\n * >({ namespace: 'Root' });\n * const serviceMessenger = new Messenger<\n *   'AnalyticsDataRegulationService',\n *   AnalyticsDataRegulationServiceActions,\n *   AnalyticsDataRegulationServiceEvents,\n *   typeof rootMessenger,\n * >({\n *   namespace: 'AnalyticsDataRegulationService',\n *   parent: rootMessenger,\n * });\n * // Instantiate the service to register its actions on the messenger\n * new AnalyticsDataRegulationService({\n *   messenger: serviceMessenger,\n *   fetch,\n *   segmentSourceId: 'abc123',\n *   segmentRegulationsEndpoint: 'https://proxy.example.com/v1beta',\n * });\n *\n * // Later...\n * // Create a data deletion task\n * const response = await rootMessenger.call(\n *   'AnalyticsDataRegulationService:createDataDeletionTask',\n *   'user-analytics-id',\n * );\n * ```\n */\nexport class AnalyticsDataRegulationService {\n  /**\n   * The name of the service.\n   */\n  readonly name: typeof serviceName;\n\n  /**\n   * The messenger suited for this service.\n   */\n  readonly #messenger: AnalyticsDataRegulationServiceMessenger;\n\n  /**\n   * A function that can be used to make an HTTP request.\n   */\n  readonly #fetch: typeof fetch;\n\n  /**\n   * Segment API source ID.\n   */\n  readonly #segmentSourceId: string;\n\n  /**\n   * Base URL for the proxy endpoint that communicates with Segment's Regulations API.\n   * This endpoint varies by environment and forwards requests to Segment API with authentication.\n   */\n  readonly #segmentRegulationsEndpoint: string;\n\n  /**\n   * The policy that wraps the request.\n   *\n   * @see {@link createServicePolicy}\n   */\n  readonly #policy: ServicePolicy;\n\n  /**\n   * Constructs a new AnalyticsDataRegulationService object.\n   *\n   * @param options - The constructor options.\n   */\n  constructor(options: AnalyticsDataRegulationServiceOptions) {\n    this.name = serviceName;\n    this.#messenger = options.messenger;\n    this.#fetch = options.fetch;\n    this.#segmentSourceId = options.segmentSourceId;\n    this.#segmentRegulationsEndpoint = options.segmentRegulationsEndpoint;\n    this.#policy = createServicePolicy(options.policyOptions ?? {});\n\n    this.#messenger.registerMethodActionHandlers(\n      this,\n      MESSENGER_EXPOSED_METHODS,\n    );\n  }\n\n  /**\n   * Registers a handler that will be called after a request returns a non-500\n   * response, causing a retry. Primarily useful in tests where timers are being\n   * mocked.\n   *\n   * @param listener - The handler to be called.\n   * @returns An object that can be used to unregister the handler.\n   * @see {@link createServicePolicy}\n   */\n  onRetry(listener: Parameters<ServicePolicy['onRetry']>[0]): IDisposable {\n    return this.#policy.onRetry(listener);\n  }\n\n  /**\n   * Registers a handler that will be called after a set number of retry rounds\n   * prove that requests to the API endpoint consistently return a 5xx response.\n   *\n   * @param listener - The handler to be called.\n   * @returns An object that can be used to unregister the handler.\n   * @see {@link createServicePolicy}\n   */\n  onBreak(listener: Parameters<ServicePolicy['onBreak']>[0]): IDisposable {\n    return this.#policy.onBreak(listener);\n  }\n\n  /**\n   * Registers a handler that will be called under one of two circumstances:\n   *\n   * 1. After a set number of retries prove that requests to the API\n   * consistently result in failures.\n   * 2. After a successful request is made to the API, but the response takes\n   * longer than a set duration to return.\n   *\n   * @param listener - The handler to be called.\n   * @returns An object that can be used to unregister the handler.\n   */\n  onDegraded(\n    listener: Parameters<ServicePolicy['onDegraded']>[0],\n  ): IDisposable {\n    return this.#policy.onDegraded(listener);\n  }\n\n  /**\n   * Creates a DELETE_ONLY regulation for the given analyticsId.\n   *\n   * @param analyticsId - The analytics ID of the user for whom to create the deletion task.\n   * @returns Promise resolving to a successful deletion regulation response.\n   * @throws Error if the request fails or the response is invalid.\n   */\n  async createDataDeletionTask(analyticsId: string): Promise<{\n    status: typeof DATA_DELETE_RESPONSE_STATUSES.Success;\n    regulateId: string;\n  }> {\n    if (!this.#segmentSourceId || !this.#segmentRegulationsEndpoint) {\n      throw new Error('Segment API source ID or endpoint not found');\n    }\n\n    const url = `${this.#segmentRegulationsEndpoint}/regulations/sources/${this.#segmentSourceId}`;\n    const body = JSON.stringify({\n      regulationType: SEGMENT_REGULATION_TYPE_DELETE_ONLY,\n      subjectType: SEGMENT_SUBJECT_TYPE_USER_ID,\n      subjectIds: [analyticsId],\n    });\n\n    const response = await this.#policy.execute(async () => {\n      const localResponse = await this.#fetch(url, {\n        method: 'POST',\n        headers: {\n          'Content-Type': SEGMENT_CONTENT_TYPE,\n        },\n        body,\n      });\n\n      if (!localResponse.ok) {\n        throw new HttpError(\n          localResponse.status,\n          `Creating data deletion task failed with status '${localResponse.status}'`,\n        );\n      }\n\n      return localResponse;\n    });\n\n    const jsonResponse = (await response.json()) as CreateRegulationResponse;\n\n    if (\n      !jsonResponse?.data?.data?.regulateId ||\n      typeof jsonResponse.data.data.regulateId !== 'string' ||\n      jsonResponse.data.data.regulateId.trim() === ''\n    ) {\n      throw new Error(\n        'Malformed response from Segment API: missing or invalid regulateId',\n      );\n    }\n\n    return {\n      status: DATA_DELETE_RESPONSE_STATUSES.Success,\n      regulateId: jsonResponse.data.data.regulateId,\n    };\n  }\n\n  /**\n   * Checks the status of a regulation by ID.\n   *\n   * @param regulationId - The regulation ID to check.\n   * @returns Promise resolving to a successful regulation status response.\n   * @throws Error if the request fails or the response is invalid.\n   */\n  async checkDataDeleteStatus(regulationId: string): Promise<{\n    status: typeof DATA_DELETE_RESPONSE_STATUSES.Success;\n    dataDeleteStatus: DataDeleteStatus;\n  }> {\n    if (!regulationId || !this.#segmentRegulationsEndpoint) {\n      throw new Error('Regulation ID or endpoint not configured');\n    }\n\n    const url = `${this.#segmentRegulationsEndpoint}/regulations/${regulationId}`;\n\n    const response = await this.#policy.execute(async () => {\n      const localResponse = await this.#fetch(url, {\n        method: 'GET',\n        headers: {\n          'Content-Type': SEGMENT_CONTENT_TYPE,\n        },\n      });\n\n      if (!localResponse.ok) {\n        throw new HttpError(\n          localResponse.status,\n          `Checking data deletion status failed with status '${localResponse.status}'`,\n        );\n      }\n\n      return localResponse;\n    });\n\n    const jsonResponse = (await response.json()) as GetRegulationStatusResponse;\n\n    const rawStatus = jsonResponse?.data?.data?.regulation?.overallStatus;\n    const dataDeleteStatus = isDataDeleteStatus(rawStatus)\n      ? rawStatus\n      : DATA_DELETE_STATUSES.Unknown;\n\n    return {\n      status: DATA_DELETE_RESPONSE_STATUSES.Success,\n      dataDeleteStatus,\n    };\n  }\n}\n"]}