       gAllowListClient.on("sync", this._onSync);
        const records = await gAllowListClient.get();
        this.updateAllowList(records);
      }
    } catch (error) {
      lazy.logConsole.error(
        `AttributionParent: failed to retrieve allow list: ${error}`
      );
    }
  }

  /**
   * Handles Remote Settings sync events.
   * Updates the allow list when the collection changes.
   *
   * @param {object} event - The sync event object.
   * @param {Array} event.data.current - The current records after sync.
   */
  onSync({ data: { current } }) {
    this.updateAllowList(current);
  }

  didDestroy() {
    if (gAllowListClient) {
      gAllowListClient.off("sync", this._onSync);
    }
  }

  /**
   * Validates a conversion event payload from an advertiser.
   * Ensures all required fields are present, correctly typed, and within valid ranges.
   *
   * @param {*} data - The conversion data to validate.
   * @returns {object|null} The validated conversion data object, or null if validation fails.
   *
   * Validation checks:
   * - Must be a plain object
   * - Must contain only allowed keys (partnerId, impressionType, lookbackDays)
   * - partnerId: must be a non-empty string
   * - impressionType: must be a string
   * - lookbackDays: must be a positive number
   */
  validateConversion(data) {
    // confirm that data is an object
    if (!isPlainObject(data)) {
      return null;
    }

    // Check that only allowed keys are present
    for (const key of Object.keys(data)) {
      if (!CONVERSION_KEYS.has(key)) {
        return null;
      }
    }

    // Validate required fields are present
    if (
      !data.partnerId ||
      !data.impressionType ||
      data.lookbackDays === undefined
    ) {
      return null;
    }

    // Validate types
    if (typeof data.partnerId !== "string") {
      return null;
    }

    if (typeof data.impressionType !== "string") {
      return null;
    }

    if (typeof data.lookbackDays !== "number" || data.lookbackDays <= 0) {
      return null;
    }

    return data;
  }

  /**
   * Receives and processes conversion event messages from the child actor.
   * This method is called when a FirefoxConversionNotification custom event is triggered
   * on an advertiser's website.
   *
   * @param {object} message - The message from the child actor.
   * @param {object} message.data - The message data.
   * @param {object} message.data.detail - The custom event detail.
   * @param {object} message.data.detail.conversion - The conversion payload.
   * @returns {Promise}
   */
  async receiveMessage(message) {
    let principal = this.manager.documentPrincipal;

    // Only accept conversion events from secure origins (HTTPS)
    if (!principal.isOriginPotentiallyTrustworthy) {
      lazy.logConsole.error(
        `AttributionParent: conversion events must be sent over HTTPS`
      );
      return;
    }

    if (!gAllowList.size) {
      await this.retrieveAllowList();
    }

    // Only accept conversion events from allowlisted origins
    if (!gAllowList.has(principal.originNoSuffix)) {
      lazy.logConsole.error(
        `AttributionParent: conversion events must come from the allow list`
      );
      return;
    }

    const { detail } = message.data || {};

    if (detail) {
      const validatedConversion = this.validateConversion(detail);

      if (!validatedConversion) {
        lazy.logConsole.error(
          `AttributionParent: rejected invalid conversion payload from ${principal}`
        );
        return;
      }

      const { partnerId, lookbackDays, impressionType } = validatedConversion;
      await newTabAttributionService.onAttributionConversion(
        partnerId,
        lookbackDays,
        impressionType
      );
    }
  }
}
PK