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