diff --git a/packages/browser-destinations/destinations/facebook-conversions-api-web/metadata.json b/packages/browser-destinations/destinations/facebook-conversions-api-web/metadata.json index 2ac166d4798..f20b60bceb8 100644 --- a/packages/browser-destinations/destinations/facebook-conversions-api-web/metadata.json +++ b/packages/browser-destinations/destinations/facebook-conversions-api-web/metadata.json @@ -118,6 +118,14 @@ { "label": "LDU enabled - Minnesota only", "value": "Minnesota" + }, + { + "label": "LDU enabled - Maryland only", + "value": "Maryland" + }, + { + "label": "LDU enabled - Rhode Island only", + "value": "RhodeIsland" } ], "default": "Disabled", @@ -148,205 +156,126 @@ "hooks": null, "dynamicFields": null, "fields": { - "event_config": { - "label": "Event Configuration", - "description": "Specify the type of Facebook Conversions API event to send.", - "type": "object", + "event_name": { + "label": "Event Name", + "description": "Facebook Conversions API Event Name to send. Select 'Custom Event' to send a non standard event.", + "type": "string", "required": true, "multiple": false, "allowNull": false, "dynamic": false, - "default": { - "event_name": "CustomEvent", - "custom_event_name": { - "@path": "$.event" + "default": "CustomEvent", + "choices": [ + { + "label": "Custom Event", + "value": "CustomEvent" }, - "show_fields": false - }, - "choices": null, - "placeholder": null, - "properties": { - "event_name": { - "label": "Event Name", - "description": "Facebook Conversions API Event Name to send. Select 'Custom Event' to send a non standard event.", - "type": "string", - "required": true, - "multiple": false, - "allowNull": false, - "dynamic": false, - "default": null, - "choices": [ - { - "label": "Custom Event", - "value": "CustomEvent" - }, - { - "label": "Page View", - "value": "PageView" - }, - { - "label": "Add Payment Info", - "value": "AddPaymentInfo" - }, - { - "label": "Add To Cart", - "value": "AddToCart" - }, - { - "label": "Add To Wishlist", - "value": "AddToWishlist" - }, - { - "label": "Complete Registration", - "value": "CompleteRegistration" - }, - { - "label": "Contact", - "value": "Contact" - }, - { - "label": "Customize Product", - "value": "CustomizeProduct" - }, - { - "label": "Donate", - "value": "Donate" - }, - { - "label": "Find Location", - "value": "FindLocation" - }, - { - "label": "Initiate Checkout", - "value": "InitiateCheckout" - }, - { - "label": "Lead", - "value": "Lead" - }, - { - "label": "Purchase", - "value": "Purchase" - }, - { - "label": "Schedule", - "value": "Schedule" - }, - { - "label": "Search", - "value": "Search" - }, - { - "label": "Start Trial", - "value": "StartTrial" - }, - { - "label": "Submit Application", - "value": "SubmitApplication" - }, - { - "label": "Subscribe", - "value": "Subscribe" - }, - { - "label": "View Content", - "value": "ViewContent" - } - ], - "placeholder": null, - "properties": null, - "category": null, - "depends_on": null, - "readOnly": null, - "hidden": null, - "minimum": null, - "maximum": null, - "defaultObjectUI": null, - "disabledInputMethods": null, - "displayMode": null, - "format": null, - "additionalProperties": false + { + "label": "Page View", + "value": "PageView" }, - "custom_event_name": { - "label": "Custom Event Name", - "description": "Custom event name to send to Facebook", - "type": "string", - "required": false, - "multiple": false, - "allowNull": false, - "dynamic": false, - "default": null, - "choices": null, - "placeholder": null, - "properties": null, - "category": null, - "depends_on": { - "match": "any", - "conditions": [ - { - "fieldKey": "event_config.show_fields", - "operator": "is", - "value": "true" - } - ] - }, - "readOnly": null, - "hidden": null, - "minimum": null, - "maximum": null, - "defaultObjectUI": null, - "disabledInputMethods": null, - "displayMode": null, - "format": null, - "additionalProperties": false + { + "label": "Add Payment Info", + "value": "AddPaymentInfo" }, - "show_fields": { - "label": "Show all fields", - "description": "Show all fields, even those which are not relevant to the selected Event Name.", - "type": "boolean", - "required": false, - "multiple": false, - "allowNull": false, - "dynamic": false, - "default": false, - "choices": null, - "placeholder": null, - "properties": null, - "category": null, - "depends_on": null, - "readOnly": null, - "hidden": null, - "minimum": null, - "maximum": null, - "defaultObjectUI": null, - "disabledInputMethods": null, - "displayMode": null, - "format": null, - "additionalProperties": false + { + "label": "Add To Cart", + "value": "AddToCart" + }, + { + "label": "Add To Wishlist", + "value": "AddToWishlist" + }, + { + "label": "Complete Registration", + "value": "CompleteRegistration" + }, + { + "label": "Contact", + "value": "Contact" + }, + { + "label": "Customize Product", + "value": "CustomizeProduct" + }, + { + "label": "Donate", + "value": "Donate" + }, + { + "label": "Find Location", + "value": "FindLocation" + }, + { + "label": "Initiate Checkout", + "value": "InitiateCheckout" + }, + { + "label": "Lead", + "value": "Lead" + }, + { + "label": "Purchase", + "value": "Purchase" + }, + { + "label": "Schedule", + "value": "Schedule" + }, + { + "label": "Search", + "value": "Search" + }, + { + "label": "Start Trial", + "value": "StartTrial" + }, + { + "label": "Submit Application", + "value": "SubmitApplication" + }, + { + "label": "Subscribe", + "value": "Subscribe" + }, + { + "label": "View Content", + "value": "ViewContent" } - }, + ], + "placeholder": null, + "properties": null, "category": null, "depends_on": null, "readOnly": null, "hidden": null, "minimum": null, "maximum": null, - "defaultObjectUI": "keyvalue", + "defaultObjectUI": null, "disabledInputMethods": null, "displayMode": null, "format": null, "additionalProperties": false }, - "content_category": { - "label": "Content Category", - "description": "The category of the content associated with the event.", + "custom_event_name": { + "label": "Custom Event Name", + "description": "Custom event name to send to Facebook", "type": "string", - "required": false, + "required": { + "match": "all", + "conditions": [ + { + "fieldKey": "event_name", + "operator": "is", + "value": "CustomEvent" + } + ] + }, "multiple": false, "allowNull": false, "dynamic": false, "default": { - "@path": "$.properties.category" + "@path": "$.event" }, "choices": null, "placeholder": null, @@ -356,24 +285,14 @@ "match": "any", "conditions": [ { - "fieldKey": "event_config.show_fields", + "fieldKey": "show_fields", "operator": "is", - "value": "true" + "value": true }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", - "value": "PageView" - }, - { - "fieldKey": "event_config.event_name", - "operator": "is", - "value": "ViewContent" - }, - { - "fieldKey": "event_config.event_name", - "operator": "is", - "value": "Search" + "value": "CustomEvent" } ] }, @@ -387,6 +306,56 @@ "format": null, "additionalProperties": false }, + "show_fields": { + "label": "Show all fields", + "description": "Show all fields, even those which are not relevant to the selected Event Name.", + "type": "boolean", + "required": false, + "multiple": false, + "allowNull": false, + "dynamic": false, + "default": false, + "choices": null, + "placeholder": null, + "properties": null, + "category": null, + "depends_on": null, + "readOnly": null, + "hidden": null, + "minimum": null, + "maximum": null, + "defaultObjectUI": null, + "disabledInputMethods": null, + "displayMode": null, + "format": null, + "additionalProperties": false + }, + "content_category": { + "label": "Content Category", + "description": "The category of the content associated with the event.", + "type": "string", + "required": false, + "multiple": false, + "allowNull": false, + "dynamic": false, + "default": { + "@path": "$.properties.category" + }, + "choices": null, + "placeholder": null, + "properties": null, + "category": null, + "depends_on": null, + "readOnly": null, + "hidden": null, + "minimum": null, + "maximum": null, + "defaultObjectUI": null, + "disabledInputMethods": null, + "displayMode": null, + "format": null, + "additionalProperties": false + }, "content_ids": { "label": "Content IDs", "description": "Product IDs associated with the event, such as SKUs (e.g. ['ABC123', 'XYZ789']). Accepts a single string value or array of strings.", @@ -395,9 +364,7 @@ "multiple": true, "allowNull": false, "dynamic": false, - "default": { - "@liquid": "{{ properties.products | map: 'product_id' }}" - }, + "default": null, "choices": null, "placeholder": null, "properties": null, @@ -406,42 +373,42 @@ "match": "any", "conditions": [ { - "fieldKey": "event_config.show_fields", + "fieldKey": "show_fields", "operator": "is", - "value": "true" + "value": true }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "AddPaymentInfo" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "AddToCart" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "AddToWishlist" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "InitiateCheckout" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Purchase" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Search" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "ViewContent" } @@ -472,31 +439,7 @@ "placeholder": null, "properties": null, "category": null, - "depends_on": { - "match": "any", - "conditions": [ - { - "fieldKey": "event_config.show_fields", - "operator": "is", - "value": "true" - }, - { - "fieldKey": "event_config.event_name", - "operator": "is", - "value": "PageView" - }, - { - "fieldKey": "event_config.event_name", - "operator": "is", - "value": "ViewContent" - }, - { - "fieldKey": "event_config.event_name", - "operator": "is", - "value": "Search" - } - ] - }, + "depends_on": null, "readOnly": null, "hidden": null, "minimum": null, @@ -533,27 +476,27 @@ "match": "any", "conditions": [ { - "fieldKey": "event_config.show_fields", + "fieldKey": "show_fields", "operator": "is", - "value": "true" + "value": true }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "AddToCart" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Purchase" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Search" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "ViewContent" } @@ -674,42 +617,42 @@ "match": "any", "conditions": [ { - "fieldKey": "event_config.show_fields", + "fieldKey": "show_fields", "operator": "is", - "value": "true" + "value": true }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "AddPaymentInfo" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "AddToCart" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "AddToWishlist" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "InitiateCheckout" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Purchase" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Search" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "ViewContent" } @@ -733,7 +676,7 @@ "match": "all", "conditions": [ { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Purchase" } @@ -1470,62 +1413,62 @@ "match": "any", "conditions": [ { - "fieldKey": "event_config.show_fields", + "fieldKey": "show_fields", "operator": "is", - "value": "true" + "value": true }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "AddPaymentInfo" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "AddToCart" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "AddToWishlist" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "CompleteRegistration" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "InitiateCheckout" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Lead" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Purchase" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Search" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "StartTrial" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Subscribe" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "ViewContent" } @@ -1571,17 +1514,17 @@ "match": "any", "conditions": [ { - "fieldKey": "event_config.show_fields", + "fieldKey": "show_fields", "operator": "is", - "value": "true" + "value": true }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Purchase" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "InitiateCheckout" } @@ -1616,15 +1559,25 @@ "match": "any", "conditions": [ { - "fieldKey": "event_config.show_fields", + "fieldKey": "show_fields", + "operator": "is", + "value": true + }, + { + "fieldKey": "event_name", + "operator": "is", + "value": "InitiateCheckout" + }, + { + "fieldKey": "event_name", "operator": "is", - "value": "true" + "value": "Purchase" } ] }, "readOnly": null, "hidden": null, - "minimum": null, + "minimum": 0, "maximum": null, "defaultObjectUI": null, "disabledInputMethods": null, @@ -1649,37 +1602,37 @@ "match": "any", "conditions": [ { - "fieldKey": "event_config.show_fields", + "fieldKey": "show_fields", "operator": "is", - "value": "true" + "value": true }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Purchase" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Subscribe" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "StartTrial" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "CompleteRegistration" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "AddPaymentInfo" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "CustomEvent" } @@ -1687,7 +1640,7 @@ }, "readOnly": null, "hidden": null, - "minimum": null, + "minimum": 0, "maximum": null, "defaultObjectUI": null, "disabledInputMethods": null, @@ -1714,15 +1667,20 @@ "match": "any", "conditions": [ { - "fieldKey": "event_config.show_fields", + "fieldKey": "show_fields", + "operator": "is", + "value": true + }, + { + "fieldKey": "event_name", "operator": "is", - "value": "true" + "value": "Purchase" } ] }, "readOnly": null, "hidden": null, - "minimum": null, + "minimum": 0, "maximum": null, "defaultObjectUI": null, "disabledInputMethods": null, @@ -1749,9 +1707,14 @@ "match": "any", "conditions": [ { - "fieldKey": "event_config.show_fields", + "fieldKey": "show_fields", "operator": "is", - "value": "true" + "value": true + }, + { + "fieldKey": "event_name", + "operator": "is", + "value": "Search" } ] }, @@ -1782,9 +1745,14 @@ "match": "any", "conditions": [ { - "fieldKey": "event_config.show_fields", + "fieldKey": "show_fields", "operator": "is", - "value": "true" + "value": true + }, + { + "fieldKey": "event_name", + "operator": "is", + "value": "CompleteRegistration" } ] }, @@ -1806,7 +1774,7 @@ "match": "all", "conditions": [ { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Purchase" } @@ -1826,62 +1794,62 @@ "match": "any", "conditions": [ { - "fieldKey": "event_config.show_fields", + "fieldKey": "show_fields", "operator": "is", - "value": "true" + "value": true }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "AddPaymentInfo" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "AddToCart" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "AddToWishlist" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "CompleteRegistration" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "InitiateCheckout" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Lead" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Purchase" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Search" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "StartTrial" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "Subscribe" }, { - "fieldKey": "event_config.event_name", + "fieldKey": "event_name", "operator": "is", "value": "ViewContent" } @@ -1889,7 +1857,7 @@ }, "readOnly": null, "hidden": null, - "minimum": null, + "minimum": 0, "maximum": null, "defaultObjectUI": null, "disabledInputMethods": null, @@ -2150,7 +2118,7 @@ }, "ge": { "label": "Gender", - "description": "Gender of the user. If unknown leave blank.", + "description": "Gender of the user. acceptable values are \"m\" or \"male\" for male and \"f\" or \"female\" for female. If unknown leave blank.", "type": "string", "required": false, "multiple": false, @@ -2159,12 +2127,20 @@ "default": null, "choices": [ { - "label": "Male", + "label": "m", "value": "m" }, { - "label": "Female", + "label": "f", "value": "f" + }, + { + "label": "male", + "value": "male" + }, + { + "label": "female", + "value": "female" } ], "placeholder": null, @@ -2372,16 +2348,14 @@ "partnerAction": "send", "subscribe": "event = \"Payment Info Entered\"", "mapping": { - "event_config": { - "event_name": "AddPaymentInfo", - "show_fields": false + "event_name": "AddPaymentInfo", + "custom_event_name": { + "@path": "$.event" }, + "show_fields": false, "content_category": { "@path": "$.properties.category" }, - "content_ids": { - "@liquid": "{{ properties.products | map: 'product_id' }}" - }, "content_name": { "@path": "$.properties.name" }, @@ -2476,16 +2450,14 @@ "partnerAction": "send", "subscribe": "event = \"Product Added\"", "mapping": { - "event_config": { - "event_name": "AddToCart", - "show_fields": false + "event_name": "AddToCart", + "custom_event_name": { + "@path": "$.event" }, + "show_fields": false, "content_category": { "@path": "$.properties.category" }, - "content_ids": { - "@path": "$.properties.product_id" - }, "content_name": { "@path": "$.properties.name" }, @@ -2565,6 +2537,9 @@ "client_ip_address": { "@path": "$.context.traits.client_ip_address" } + }, + "content_ids": { + "@path": "$.properties.product_id" } }, "eventSlug": null @@ -2575,16 +2550,14 @@ "partnerAction": "send", "subscribe": "event = \"Product Added To Wishlist\"", "mapping": { - "event_config": { - "event_name": "AddToWishlist", - "show_fields": false + "event_name": "AddToWishlist", + "custom_event_name": { + "@path": "$.event" }, + "show_fields": false, "content_category": { "@path": "$.properties.category" }, - "content_ids": { - "@path": "$.properties.product_id" - }, "content_name": { "@path": "$.properties.name" }, @@ -2664,6 +2637,9 @@ "client_ip_address": { "@path": "$.context.traits.client_ip_address" } + }, + "content_ids": { + "@path": "$.properties.product_id" } }, "eventSlug": null @@ -2674,16 +2650,14 @@ "partnerAction": "send", "subscribe": "event = \"Signed Up\"", "mapping": { - "event_config": { - "event_name": "CompleteRegistration", - "show_fields": false + "event_name": "CompleteRegistration", + "custom_event_name": { + "@path": "$.event" }, + "show_fields": false, "content_category": { "@path": "$.properties.category" }, - "content_ids": { - "@liquid": "{{ properties.products | map: 'product_id' }}" - }, "content_name": { "@path": "$.properties.name" }, @@ -2778,16 +2752,14 @@ "partnerAction": "send", "subscribe": "event = \"Checkout Started\"", "mapping": { - "event_config": { - "event_name": "InitiateCheckout", - "show_fields": false + "event_name": "InitiateCheckout", + "custom_event_name": { + "@path": "$.event" }, + "show_fields": false, "content_category": { "@path": "$.properties.category" }, - "content_ids": { - "@liquid": "{{ properties.products | map: 'product_id' }}" - }, "content_name": { "@path": "$.properties.name" }, @@ -2882,16 +2854,14 @@ "partnerAction": "send", "subscribe": "type = \"page\"", "mapping": { - "event_config": { - "event_name": "PageView", - "show_fields": false + "event_name": "PageView", + "custom_event_name": { + "@path": "$.event" }, + "show_fields": false, "content_category": { "@path": "$.category" }, - "content_ids": { - "@liquid": "{{ properties.products | map: 'product_id' }}" - }, "content_name": { "@path": "$.name" }, @@ -2986,16 +2956,14 @@ "partnerAction": "send", "subscribe": "event = \"Order Completed\"", "mapping": { - "event_config": { - "event_name": "Purchase", - "show_fields": false + "event_name": "Purchase", + "custom_event_name": { + "@path": "$.event" }, + "show_fields": false, "content_category": { "@path": "$.properties.category" }, - "content_ids": { - "@liquid": "{{ properties.products | map: 'product_id' }}" - }, "content_name": { "@path": "$.properties.name" }, @@ -3095,16 +3063,14 @@ "partnerAction": "send", "subscribe": "event = \"Products Searched\"", "mapping": { - "event_config": { - "event_name": "Search", - "show_fields": false + "event_name": "Search", + "custom_event_name": { + "@path": "$.event" }, + "show_fields": false, "content_category": { "@path": "$.properties.category" }, - "content_ids": { - "@path": "$.properties.product_id" - }, "content_name": { "@path": "$.properties.name" }, @@ -3184,6 +3150,9 @@ "client_ip_address": { "@path": "$.context.traits.client_ip_address" } + }, + "content_ids": { + "@path": "$.properties.product_id" } }, "eventSlug": null @@ -3194,16 +3163,14 @@ "partnerAction": "send", "subscribe": "event = \"Product Viewed\"", "mapping": { - "event_config": { - "event_name": "ViewContent", - "show_fields": false + "event_name": "ViewContent", + "custom_event_name": { + "@path": "$.event" }, + "show_fields": false, "content_category": { "@path": "$.properties.category" }, - "content_ids": { - "@path": "$.properties.product_id" - }, "content_name": { "@path": "$.properties.name" }, @@ -3283,6 +3250,9 @@ "client_ip_address": { "@path": "$.context.traits.client_ip_address" } + }, + "content_ids": { + "@path": "$.properties.product_id" } }, "eventSlug": null diff --git a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/index.ts b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/index.ts index 29aa6d805bc..1a39055deac 100644 --- a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/index.ts +++ b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/index.ts @@ -8,13 +8,16 @@ import { defaultValues } from '@segment/actions-core' declare global { interface Window { - fbq: FBClient, + fbq: FBClient _fbq: FBClient clientParamBuilder: FBClientParamBuilder | undefined } } -export const destination: BrowserDestinationDefinition = { +export const destination: BrowserDestinationDefinition< + Settings, + { fbq: FBClient; clientParamBuilder: FBClientParamBuilder | undefined } +> = { name: 'Facebook Conversions Api Web', slug: 'actions-facebook-conversions-api-web', mode: 'device', @@ -24,22 +27,25 @@ export const destination: BrowserDestinationDefinition typeof window.fbq === 'function', 100) - if(formatUserDataWithParamBuilder){ - const script = `https://capi-automation.s3.us-east-2.amazonaws.com/public/client_js/capiParamBuilder/clientParamBuilder.bundle.js` + if (formatUserDataWithParamBuilder) { + const script = `https://unpkg.com/meta-capi-param-builder-clientjs/dist/clientParamBuilder.bundle.js` await deps.loadScript(script) await deps.resolveWhen(() => typeof window.clientParamBuilder === 'object', 100) } @@ -99,13 +108,10 @@ export const destination: BrowserDestinationDefinition { + describe('field definition', () => { + it('is a multiple string field', () => { + expect(content_ids.type).toBe('string') + expect(content_ids.multiple).toBe(true) + }) + + // Regression guard: the field previously shipped a broken default that + // used the disabled Liquid `map` filter + // ({ '@liquid': "{{ properties.products | map: 'product_id' }}" }). + // That default threw / never resolved to an array, so content_ids was + // always dropped. We removed the default; customers configure it if + // needed. Do NOT reintroduce a default without verifying it resolves to + // an array of strings AND renders in the app mapping-editor UI. + it('has no default mapping', () => { + expect(content_ids.default).toBeUndefined() + }) + + it('is gated behind the relevant event dependencies', () => { + expect(content_ids.depends_on).toBeDefined() + }) + }) + + describe('formatFBEvent handling of content_ids', () => { + it('passes a provided array of ids through', () => { + const payload: Partial = { + event_name: 'ViewContent', + show_fields: true, + content_ids: ['SKU-ABC-123', 'SKU-XYZ-789'] + } + + const result = formatFBEvent(payload as Payload) + + expect(result.content_ids).toEqual(['SKU-ABC-123', 'SKU-XYZ-789']) + }) + + it('omits content_ids when it is an empty array', () => { + const payload: Partial = { + event_name: 'AddToCart', + show_fields: true, + content_ids: [] + } + + const result = formatFBEvent(payload as Payload) + + expect(result).not.toHaveProperty('content_ids') + }) + + it('omits content_ids when it is absent', () => { + const payload: Partial = { + event_name: 'Purchase', + show_fields: true, + value: 45.97 + } + + const result = formatFBEvent(payload as Payload) + + expect(result).not.toHaveProperty('content_ids') + }) + }) +}) diff --git a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/depends-on.test.ts b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/depends-on.test.ts index 0c96e506d2d..34cbd5f250a 100644 --- a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/depends-on.test.ts +++ b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/depends-on.test.ts @@ -1,28 +1,55 @@ import { getDependenciesFor } from '../depends-on' describe('depends-on getDependenciesFor', () => { - test('returns correct depends_on rules', () => { + test('multi-event field includes show_fields plus one condition per event', () => { const result = getDependenciesFor('delivery_category') - expect(true).toBe(true) expect(result.match).toBe('any') - expect(result.conditions).toEqual( - [ - { - fieldKey: 'event_config.show_fields', - operator: 'is', - value: 'true' - }, - { - fieldKey: 'event_config.event_name', - operator: 'is', - value: 'Purchase' - }, - { - fieldKey: 'event_config.event_name', - operator: 'is', - value: 'InitiateCheckout' - } - ] - ) - }) + expect(result.conditions).toEqual([ + { + fieldKey: 'show_fields', + operator: 'is', + value: true + }, + { + fieldKey: 'event_name', + operator: 'is', + value: 'Purchase' + }, + { + fieldKey: 'event_name', + operator: 'is', + value: 'InitiateCheckout' + } + ]) + }) + + test('single-event field still includes its event condition', () => { + // status is relevant to exactly one event (CompleteRegistration); it must + // still gate on that event, not only on show_fields. + const result = getDependenciesFor('status') + expect(result.match).toBe('any') + expect(result.conditions).toEqual([ + { fieldKey: 'show_fields', operator: 'is', value: true }, + { fieldKey: 'event_name', operator: 'is', value: 'CompleteRegistration' } + ]) + }) + + test('custom_event_name gates on show_fields and the CustomEvent event', () => { + const result = getDependenciesFor('custom_event_name') + expect(result.conditions).toEqual([ + { fieldKey: 'show_fields', operator: 'is', value: true }, + { fieldKey: 'event_name', operator: 'is', value: 'CustomEvent' } + ]) + }) + + test('show_fields condition uses a boolean value, not the string "true"', () => { + const result = getDependenciesFor('status') + const showFieldsCondition = result.conditions.find((c) => 'fieldKey' in c && c.fieldKey === 'show_fields') + expect(showFieldsCondition).toEqual({ fieldKey: 'show_fields', operator: 'is', value: true }) + }) + + test('unknown field returns only the show_fields condition', () => { + const result = getDependenciesFor('not_a_real_field') + expect(result.conditions).toEqual([{ fieldKey: 'show_fields', operator: 'is', value: true }]) + }) }) diff --git a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/formatFBEvent.test.ts b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/formatFBEvent.test.ts index 7bc5adcec43..424a827a0a5 100644 --- a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/formatFBEvent.test.ts +++ b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/formatFBEvent.test.ts @@ -2,12 +2,72 @@ import { formatFBEvent } from '../functions' import { Payload } from '../generated-types' describe('formatFBEvent', () => { + it('should normalize string, array, and currency fields', () => { + const payload = { + event_name: 'Purchase', + show_fields: true, + content_category: ' Games ', + content_name: ' Monopoly ', + content_type: ' product ', + content_ids: [' SKU-ABC-123 ', ' ', 'SKU-XYZ-789'], + delivery_category: ' home_delivery ', + search_string: ' board game ', + currency: ' usd ', + value: 45.97 + } as unknown as Payload + + const result = formatFBEvent(payload) + + expect(result).toEqual({ + partner_agent: 'segment', + content_category: 'Games', + content_name: 'Monopoly', + content_type: 'product', + content_ids: ['SKU-ABC-123', 'SKU-XYZ-789'], // trimmed; whitespace-only entry dropped + delivery_category: 'home_delivery', + search_string: 'board game', + currency: 'USD', // trimmed + uppercased to a valid ISO code + value: 45.97 + }) + }) + + it('should drop an invalid currency code', () => { + const payload = { + event_name: 'Purchase', + show_fields: true, + content_ids: ['SKU-ABC-123'], + currency: 'NOTACURRENCY', + value: 10 + } as unknown as Payload + + const result = formatFBEvent(payload) + + expect(result).not.toHaveProperty('currency') + }) + + it('should trim string values inside contents items', () => { + const payload = { + event_name: 'AddToCart', + show_fields: true, + contents: [ + { id: ' SKU-ABC-123 ', quantity: 2, item_price: 19.99 }, + { id: 'SKU-XYZ-789 ', quantity: 1 } + ] + } as unknown as Payload + + const result = formatFBEvent(payload) + + // string id trimmed; numeric quantity/item_price untouched + expect(result.contents).toEqual([ + { id: 'SKU-ABC-123', quantity: 2, item_price: 19.99 }, + { id: 'SKU-XYZ-789', quantity: 1 } + ]) + }) + it('should format a complete Purchase event with all fields', () => { const payload: Partial = { - event_config: { - event_name: 'Purchase', - show_fields: true - }, + event_name: 'Purchase', + show_fields: true, content_ids: ['product-123', 'product-456'], content_name: 'Test Product', content_category: 'Electronics', @@ -55,10 +115,8 @@ describe('formatFBEvent', () => { it('should format minimal PageView event', () => { const payload: Partial = { - event_config: { - event_name: 'PageView', - show_fields: false - } + event_name: 'PageView', + show_fields: false } const result = formatFBEvent(payload as Payload) @@ -70,10 +128,8 @@ describe('formatFBEvent', () => { it('should include only provided fields', () => { const payload: Partial = { - event_config: { - event_name: 'ViewContent', - show_fields: true - }, + event_name: 'ViewContent', + show_fields: true, content_ids: ['product-789'], value: 149.99, currency: 'USD' @@ -91,10 +147,8 @@ describe('formatFBEvent', () => { it('should handle zero values for numeric fields', () => { const payload: Partial = { - event_config: { - event_name: 'Purchase', - show_fields: true - }, + event_name: 'Purchase', + show_fields: true, content_ids: ['product-123'], value: 0, num_items: 0, @@ -116,10 +170,8 @@ describe('formatFBEvent', () => { it('should not include empty arrays', () => { const payload: Partial = { - event_config: { - event_name: 'AddToCart', - show_fields: true - }, + event_name: 'AddToCart', + show_fields: true, content_ids: [], contents: [], value: 99.99 @@ -135,10 +187,8 @@ describe('formatFBEvent', () => { it('should not include empty custom_data object', () => { const payload: Partial = { - event_config: { - event_name: 'Purchase', - show_fields: true - }, + event_name: 'Purchase', + show_fields: true, content_ids: ['product-123'], value: 99.99, custom_data: {} @@ -155,16 +205,14 @@ describe('formatFBEvent', () => { it('should include contents array with all item properties', () => { const payload: Partial = { - event_config: { - event_name: 'Purchase', - show_fields: true - }, + event_name: 'Purchase', + show_fields: true, contents: [ - { id: 'product-1', quantity: 2, item_price: 25.50 }, - { id: 'product-2', quantity: 1, item_price: 100.00 }, + { id: 'product-1', quantity: 2, item_price: 25.5 }, + { id: 'product-2', quantity: 1, item_price: 100.0 }, { id: 'product-3', quantity: 3 } ], - value: 151.00 + value: 151.0 } const result = formatFBEvent(payload as Payload) @@ -172,20 +220,18 @@ describe('formatFBEvent', () => { expect(result).toEqual({ partner_agent: 'segment', contents: [ - { id: 'product-1', quantity: 2, item_price: 25.50 }, - { id: 'product-2', quantity: 1, item_price: 100.00 }, + { id: 'product-1', quantity: 2, item_price: 25.5 }, + { id: 'product-2', quantity: 1, item_price: 100.0 }, { id: 'product-3', quantity: 3 } ], - value: 151.00 + value: 151.0 }) }) it('should include all standard event fields', () => { const payload: Partial = { - event_config: { - event_name: 'InitiateCheckout', - show_fields: true - }, + event_name: 'InitiateCheckout', + show_fields: true, content_category: 'Apparel', content_ids: ['shirt-123'], content_name: 'Blue Shirt', @@ -209,12 +255,67 @@ describe('formatFBEvent', () => { }) }) + it('should include search_string for a Search event', () => { + const payload: Partial = { + event_name: 'Search', + show_fields: true, + search_string: 'monopoly board game', + content_category: 'Games', + content_ids: ['product-123'], + currency: 'USD', + value: 19.99 + } + + const result = formatFBEvent(payload as Payload) + + expect(result).toEqual({ + partner_agent: 'segment', + search_string: 'monopoly board game', + content_category: 'Games', + content_ids: ['product-123'], + currency: 'USD', + value: 19.99 + }) + }) + + it('should include status for a CompleteRegistration event', () => { + const payload: Partial = { + event_name: 'CompleteRegistration', + show_fields: true, + status: true, + currency: 'USD', + value: 0 + } + + const result = formatFBEvent(payload as Payload) + + expect(result).toEqual({ + partner_agent: 'segment', + status: true, + currency: 'USD', + value: 0 + }) + }) + + it('should include status when false (registration not completed)', () => { + const payload: Partial = { + event_name: 'CompleteRegistration', + show_fields: true, + status: false + } + + const result = formatFBEvent(payload as Payload) + + expect(result).toEqual({ + partner_agent: 'segment', + status: false + }) + }) + it('should format Subscribe event with predicted_ltv', () => { const payload: Partial = { - event_config: { - event_name: 'Subscribe', - show_fields: true - }, + event_name: 'Subscribe', + show_fields: true, value: 9.99, currency: 'USD', predicted_ltv: 119.88 @@ -232,14 +333,12 @@ describe('formatFBEvent', () => { it('should format Purchase event with net_revenue', () => { const payload: Partial = { - event_config: { - event_name: 'Purchase', - show_fields: true - }, + event_name: 'Purchase', + show_fields: true, content_ids: ['product-123'], - value: 100.00, + value: 100.0, currency: 'USD', - net_revenue: 85.00 + net_revenue: 85.0 } const result = formatFBEvent(payload as Payload) @@ -247,18 +346,16 @@ describe('formatFBEvent', () => { expect(result).toEqual({ partner_agent: 'segment', content_ids: ['product-123'], - value: 100.00, + value: 100.0, currency: 'USD', - net_revenue: 85.00 + net_revenue: 85.0 }) }) it('should include custom_data when provided', () => { const payload: Partial = { - event_config: { - event_name: 'Lead', - show_fields: true - }, + event_name: 'Lead', + show_fields: true, value: 0, custom_data: { lead_source: 'facebook_ad', diff --git a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/formatUserData.test.ts b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/formatUserData.test.ts index 34073cd7f11..fd8e7eb7036 100644 --- a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/formatUserData.test.ts +++ b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/formatUserData.test.ts @@ -61,6 +61,24 @@ describe('formatUserData', () => { }) }) + it('should normalize word-form gender before hashing', async () => { + // 'Female' should normalize to 'f' -> sha256('f') + const result = await formatUserData({ ge: 'Female' } as Payload['userData'], undefined) + expect(result).toEqual({ + ge: '252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111' + }) + + // 'male' should normalize to 'm' -> sha256('m') + const male = await formatUserData({ ge: 'male' } as Payload['userData'], undefined) + expect(male).toEqual({ + ge: '62c66a7a5dd70c3146618063c344e531e6d4b59e379808443ce962b3abd63c5a' + }) + + // an unrecognized value is dropped + const unknown = await formatUserData({ ge: 'nonbinary' } as Payload['userData'], undefined) + expect(unknown).toBeUndefined() + }) + it('should format date of birth as YYYYMMDD and hash', async () => { const userData: Payload['userData'] = { db: '1990-05-15T00:00:00.000Z' diff --git a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/functions.test.ts b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/functions.test.ts index 5ddc0029f19..971b3a68e6d 100644 --- a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/functions.test.ts +++ b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/functions.test.ts @@ -31,10 +31,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { describe('send - Standard Events', () => { it('should send Purchase event with required fields', async () => { const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, currency: 'USD' @@ -58,10 +56,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { it('should send AddToCart event with contents', async () => { const payload = { - event_config: { - event_name: 'AddToCart', - show_fields: false - }, + event_name: 'AddToCart', + show_fields: false, contents: [ { id: 'product-123', @@ -89,12 +85,63 @@ describe('Facebook Conversions API Web - Send Functions', () => { ) }) - it('should send ViewContent event', async () => { + it('should wrap a single content_ids string into an array', async () => { + // The browser device-mode runtime does not arrify multiple:true fields, + // so a single-product event maps content_ids to a scalar string. It + // should still be sent to fbq as an array, not dropped. const payload = { - event_config: { - event_name: 'ViewContent', - show_fields: false + event_name: 'ViewContent', + show_fields: false, + content_ids: 'product-123', + value: 19.99 + } + + await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) + + expect(mockFbq).toHaveBeenCalledWith( + 'trackSingle', + 'test-pixel-123', + 'ViewContent', + { + partner_agent: 'segment', + content_ids: ['product-123'], + value: 19.99 }, + undefined + ) + }) + + it('should wrap a single contents object into an array', async () => { + // Likewise a single-product event maps contents to a scalar object; it + // should be sent to fbq wrapped in an array. + const payload = { + event_name: 'AddToCart', + show_fields: false, + contents: { id: 'product-123', quantity: 2, item_price: 49.99 }, + value: 99.98, + currency: 'USD' + } + + await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) + + expect(mockFbq).toHaveBeenCalledWith( + 'trackSingle', + 'test-pixel-123', + 'AddToCart', + { + partner_agent: 'segment', + contents: [{ id: 'product-123', quantity: 2, item_price: 49.99 }], + value: 99.98, + currency: 'USD' + }, + undefined + ) + }) + + it('should send ViewContent event', async () => { + const payload = { + event_name: 'ViewContent', + show_fields: false, content_ids: ['product-456'], content_name: 'Test Product', content_category: 'Electronics', @@ -118,28 +165,84 @@ describe('Facebook Conversions API Web - Send Functions', () => { ) }) + it('should send Search event with search_string', async () => { + const payload = { + event_name: 'Search', + show_fields: false, + search_string: 'monopoly board game', + content_category: 'Games', + content_ids: ['product-123'], + currency: 'USD', + value: 19.99 + } + + await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) + + expect(mockFbq).toHaveBeenCalledWith( + 'trackSingle', + 'test-pixel-123', + 'Search', + { + partner_agent: 'segment', + search_string: 'monopoly board game', + content_category: 'Games', + content_ids: ['product-123'], + currency: 'USD', + value: 19.99 + }, + undefined + ) + }) + + it('should send CompleteRegistration event with status', async () => { + const payload = { + event_name: 'CompleteRegistration', + show_fields: false, + status: true, + currency: 'USD', + value: 0 + } + + await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) + + expect(mockFbq).toHaveBeenCalledWith( + 'trackSingle', + 'test-pixel-123', + 'CompleteRegistration', + { + partner_agent: 'segment', + status: true, + currency: 'USD', + value: 0 + }, + undefined + ) + }) + it('should send PageView event', async () => { const payload = { - event_config: { - event_name: 'PageView', - show_fields: false - } + event_name: 'PageView', + show_fields: false } await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) - expect(mockFbq).toHaveBeenCalledWith('trackSingle', 'test-pixel-123', 'PageView', { partner_agent: 'segment' }, undefined) + expect(mockFbq).toHaveBeenCalledWith( + 'trackSingle', + 'test-pixel-123', + 'PageView', + { partner_agent: 'segment' }, + undefined + ) }) }) describe('send - Custom Events', () => { it('should send custom event with custom event name', async () => { const payload = { - event_config: { - event_name: 'CustomEvent', - custom_event_name: 'MyCustomEvent', - show_fields: true - }, + event_name: 'CustomEvent', + custom_event_name: 'MyCustomEvent', + show_fields: true, value: 50.0, custom_data: { custom_field_1: 'value1', @@ -169,10 +272,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { describe('send - Validation', () => { it('should warn if AddToCart is missing both content_ids and contents', async () => { const payload = { - event_config: { - event_name: 'AddToCart', - show_fields: false - }, + event_name: 'AddToCart', + show_fields: false, value: 99.99 } @@ -186,10 +287,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { it('should warn if Purchase is missing both content_ids and contents', async () => { const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, value: 199.99, currency: 'USD' } @@ -204,10 +303,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { it('should warn if ViewContent is missing both content_ids and contents', async () => { const payload = { - event_config: { - event_name: 'ViewContent', - show_fields: false - } + event_name: 'ViewContent', + show_fields: false } await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) @@ -220,10 +317,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { it('should not warn if AddToCart has content_ids', async () => { const payload = { - event_config: { - event_name: 'AddToCart', - show_fields: false - }, + event_name: 'AddToCart', + show_fields: false, content_ids: ['product-123'], value: 99.99 } @@ -236,10 +331,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { it('should not warn if AddToCart has contents', async () => { const payload = { - event_config: { - event_name: 'AddToCart', - show_fields: false - }, + event_name: 'AddToCart', + show_fields: false, contents: [{ id: 'product-123', quantity: 1 }], value: 99.99 } @@ -249,15 +342,67 @@ describe('Facebook Conversions API Web - Send Functions', () => { expect(consoleWarnSpy).not.toHaveBeenCalled() expect(mockFbq).toHaveBeenCalled() }) + + it('should warn if AddToCart content_ids is only whitespace', async () => { + // Whitespace-only content_ids is dropped by formatFBEvent, so validation + // must treat it as absent rather than letting the event through empty. + const payload = { + event_name: 'AddToCart', + show_fields: false, + content_ids: [' '], + value: 99.99 + } + + await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) + + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining('At least one of content_ids or contents is required for the AddToCart event') + ) + expect(mockFbq).not.toHaveBeenCalled() + }) + + it('should warn if CustomEvent has no custom_event_name', async () => { + const payload = { + event_name: 'CustomEvent', + custom_event_name: ' ', + show_fields: false, + value: 10 + } + + await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) + + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining('custom_event_name is required when event_name is CustomEvent') + ) + expect(mockFbq).not.toHaveBeenCalled() + }) + + it('should send CustomEvent when custom_event_name is provided', async () => { + const payload = { + event_name: 'CustomEvent', + custom_event_name: 'MyCustomEvent', + show_fields: false, + value: 10 + } + + await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) + + expect(consoleWarnSpy).not.toHaveBeenCalled() + expect(mockFbq).toHaveBeenCalledWith( + 'trackSingleCustom', + 'test-pixel-123', + 'MyCustomEvent', + expect.any(Object), + undefined + ) + }) }) describe('send - Event Options', () => { it('should include eventID when provided', async () => { const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, eventID: 'unique-event-id-123' @@ -265,23 +410,15 @@ describe('Facebook Conversions API Web - Send Functions', () => { await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) - expect(mockFbq).toHaveBeenCalledWith( - 'trackSingle', - 'test-pixel-123', - 'Purchase', - expect.any(Object), - { - eventID: 'unique-event-id-123' - } - ) + expect(mockFbq).toHaveBeenCalledWith('trackSingle', 'test-pixel-123', 'Purchase', expect.any(Object), { + eventID: 'unique-event-id-123' + }) }) it('should include eventSourceUrl when provided', async () => { const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, eventSourceUrl: 'https://example.com/checkout' @@ -289,23 +426,15 @@ describe('Facebook Conversions API Web - Send Functions', () => { await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) - expect(mockFbq).toHaveBeenCalledWith( - 'trackSingle', - 'test-pixel-123', - 'Purchase', - expect.any(Object), - { - eventSourceUrl: 'https://example.com/checkout' - } - ) + expect(mockFbq).toHaveBeenCalledWith('trackSingle', 'test-pixel-123', 'Purchase', expect.any(Object), { + eventSourceUrl: 'https://example.com/checkout' + }) }) it('should include both eventID and eventSourceUrl when provided', async () => { const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, eventID: 'unique-event-id-123', @@ -314,26 +443,18 @@ describe('Facebook Conversions API Web - Send Functions', () => { await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) - expect(mockFbq).toHaveBeenCalledWith( - 'trackSingle', - 'test-pixel-123', - 'Purchase', - expect.any(Object), - { - eventID: 'unique-event-id-123', - eventSourceUrl: 'https://example.com/checkout' - } - ) + expect(mockFbq).toHaveBeenCalledWith('trackSingle', 'test-pixel-123', 'Purchase', expect.any(Object), { + eventID: 'unique-event-id-123', + eventSourceUrl: 'https://example.com/checkout' + }) }) }) describe('send - Event Data Fields', () => { it('should include all event fields when show_fields is true', async () => { const payload = { - event_config: { - event_name: 'Purchase', - show_fields: true - }, + event_name: 'Purchase', + show_fields: true, content_ids: ['product-123'], content_name: 'Test Product', content_category: 'Electronics', @@ -367,10 +488,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { it('should include predicted_ltv for Subscribe event', async () => { const payload = { - event_config: { - event_name: 'Subscribe', - show_fields: false - }, + event_name: 'Subscribe', + show_fields: false, predicted_ltv: 500.0, value: 50.0 } @@ -392,10 +511,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { it('should include net_revenue for Purchase event', async () => { const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], net_revenue: 450.0, value: 50.0 @@ -419,10 +536,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { it('should include custom_data', async () => { const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, custom_data: { @@ -454,10 +569,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { it('should not include empty arrays or objects', async () => { const payload = { - event_config: { - event_name: 'PageView', - show_fields: false - }, + event_name: 'PageView', + show_fields: false, content_ids: [], contents: [], custom_data: {} @@ -465,16 +578,14 @@ describe('Facebook Conversions API Web - Send Functions', () => { await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) - const eventData = (mockFbq).mock.calls[0][3] + const eventData = mockFbq.mock.calls[0][3] expect(eventData).toEqual({ partner_agent: 'segment' }) }) it('should handle numeric values correctly including zero', async () => { const payload = { - event_config: { - event_name: 'InitiateCheckout', - show_fields: false - }, + event_name: 'InitiateCheckout', + show_fields: false, content_ids: ['product-123'], value: 0, num_items: 0 @@ -514,10 +625,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { mockClientParamBuilderInstance.getNormalizedAndHashedPII.mockReturnValue('hashed_email_value') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -537,10 +646,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { mockClientParamBuilderInstance.getNormalizedAndHashedPII.mockReturnValue('hashed_phone_value') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -564,10 +671,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { }) const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -592,30 +697,29 @@ describe('Facebook Conversions API Web - Send Functions', () => { expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('John', 'first_name') expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('Doe', 'last_name') expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('m', 'gender') - expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('1990-05-15', 'date_of_birth') + expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith( + '1990-05-15', + 'date_of_birth' + ) expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('San Francisco', 'city') expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('CA', 'state') expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('94102', 'zip_code') expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('US', 'country') expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('user-123', 'external_id') - expect(mockFbq).toHaveBeenCalledWith( - 'init', - 'test-pixel-123', - { - em: 'hashed_email_value', - ph: 'hashed_phone_value', - fn: 'hashed_first_name_value', - ln: 'hashed_last_name_value', - ge: 'hashed_gender_value', - db: 'hashed_date_of_birth_value', - ct: 'hashed_city_value', - st: 'hashed_state_value', - zp: 'hashed_zip_code_value', - country: 'hashed_country_value', - external_id: 'hashed_external_id_value' - } - ) + expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', { + em: 'hashed_email_value', + ph: 'hashed_phone_value', + fn: 'hashed_first_name_value', + ln: 'hashed_last_name_value', + ge: 'hashed_gender_value', + db: 'hashed_date_of_birth_value', + ct: 'hashed_city_value', + st: 'hashed_state_value', + zp: 'hashed_zip_code_value', + country: 'hashed_country_value', + external_id: 'hashed_external_id_value' + }) }) it('should use clientParamBuilder getFbc and getFbp methods', async () => { @@ -625,10 +729,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { mockClientParamBuilderInstance.getNormalizedAndHashedPII.mockReturnValue('hashed_email') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -645,15 +747,11 @@ describe('Facebook Conversions API Web - Send Functions', () => { expect(mockClientParamBuilderInstance.getFbp).toHaveBeenCalled() // ClientParamBuilder values should override payload values - expect(mockFbq).toHaveBeenCalledWith( - 'init', - 'test-pixel-123', - { - em: 'hashed_email', - fbc: 'fb.1.1234567890.ClientParamBuilderFbc', - fbp: 'fb.1.1234567890.ClientParamBuilderFbp' - } - ) + expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', { + em: 'hashed_email', + fbc: 'fb.1.1234567890.ClientParamBuilderFbc', + fbp: 'fb.1.1234567890.ClientParamBuilderFbp' + }) }) it('should use payload fbc/fbp when clientParamBuilder methods return null', async () => { @@ -663,10 +761,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { mockClientParamBuilderInstance.getNormalizedAndHashedPII.mockReturnValue('hashed_email') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -682,15 +778,11 @@ describe('Facebook Conversions API Web - Send Functions', () => { expect(mockClientParamBuilderInstance.getFbp).toHaveBeenCalled() // Should fall back to payload values when clientParamBuilder returns null - expect(mockFbq).toHaveBeenCalledWith( - 'init', - 'test-pixel-123', - { - em: 'hashed_email', - fbc: 'fb.1.1234567890.PayloadFbc', - fbp: 'fb.1.1234567890.PayloadFbp' - } - ) + expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', { + em: 'hashed_email', + fbc: 'fb.1.1234567890.PayloadFbc', + fbp: 'fb.1.1234567890.PayloadFbp' + }) }) it('should fall back to default formatting when clientParamBuilder returns undefined', async () => { @@ -698,10 +790,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { mockClientParamBuilderInstance.getNormalizedAndHashedPII.mockReturnValue(undefined) const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -716,7 +806,7 @@ describe('Facebook Conversions API Web - Send Functions', () => { expect(mockClientParamBuilderInstance.getNormalizedAndHashedPII).toHaveBeenCalledWith('(555) 123-4567', 'phone') // When clientParamBuilder returns undefined, nothing should be sent (empty userData) - const initCalls = (mockFbq).mock.calls.filter((call) => call[0] === 'init') + const initCalls = mockFbq.mock.calls.filter((call) => call[0] === 'init') expect(initCalls.length).toBe(0) }) @@ -727,10 +817,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { mockClientParamBuilderInstance.getNormalizedAndHashedPII.mockReturnValue('hashed_email') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -753,10 +841,8 @@ describe('Facebook Conversions API Web - Send Functions', () => { mockAnalytics.storage.get.mockReturnValue('0') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -769,14 +855,10 @@ describe('Facebook Conversions API Web - Send Functions', () => { await send(mockFbq, undefined, payload, defaultSettings, mockAnalytics) // Should use default formatting (normalize and hash) - expect(mockFbq).toHaveBeenCalledWith( - 'init', - 'test-pixel-123', - { - em: '973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b', - ph: '3c95277da5fd0da6a1a44ee3fdf56d20af6c6d242695a40e18e6e90dc3c5872c' - } - ) + expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', { + em: '973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b', + ph: '3c95277da5fd0da6a1a44ee3fdf56d20af6c6d242695a40e18e6e90dc3c5872c' + }) }) }) }) diff --git a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/init.test.ts b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/init.test.ts index 9e48c0a0df5..563d88d8c77 100644 --- a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/init.test.ts +++ b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/__tests__/init.test.ts @@ -31,10 +31,8 @@ describe('Facebook Conversions API Web - Init with User Data', () => { mockAnalytics.storage.get.mockReturnValue('0') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -60,10 +58,8 @@ describe('Facebook Conversions API Web - Init with User Data', () => { mockAnalytics.storage.get.mockReturnValue('0') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -84,10 +80,8 @@ describe('Facebook Conversions API Web - Init with User Data', () => { mockAnalytics.storage.get.mockReturnValue('0') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -101,24 +95,18 @@ describe('Facebook Conversions API Web - Init with User Data', () => { // Names should be lowercased and trimmed, then hashed // 'john' -> '96d9632f363564cc3032521409cf22a852f2032eec099ed5967c0d000cec607a' // 'doe' -> '799ef92a11af918e3fb741df42934f3b568ed2d93ac1df74f1b8d41a27932a6f' - expect(mockFbq).toHaveBeenCalledWith( - 'init', - 'test-pixel-123', - { - fn: '96d9632f363564cc3032521409cf22a852f2032eec099ed5967c0d000cec607a', - ln: '799ef92a11af918e3fb741df42934f3b568ed2d93ac1df74f1b8d41a27932a6f' - } - ) + expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', { + fn: '96d9632f363564cc3032521409cf22a852f2032eec099ed5967c0d000cec607a', + ln: '799ef92a11af918e3fb741df42934f3b568ed2d93ac1df74f1b8d41a27932a6f' + }) }) it('should format userData with gender', async () => { mockAnalytics.storage.get.mockReturnValue('0') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -138,10 +126,8 @@ describe('Facebook Conversions API Web - Init with User Data', () => { mockAnalytics.storage.get.mockReturnValue('0') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -162,10 +148,8 @@ describe('Facebook Conversions API Web - Init with User Data', () => { mockAnalytics.storage.get.mockReturnValue('0') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -186,10 +170,8 @@ describe('Facebook Conversions API Web - Init with User Data', () => { mockAnalytics.storage.get.mockReturnValue('0') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -210,10 +192,8 @@ describe('Facebook Conversions API Web - Init with User Data', () => { mockAnalytics.storage.get.mockReturnValue('0') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -234,10 +214,8 @@ describe('Facebook Conversions API Web - Init with User Data', () => { mockAnalytics.storage.get.mockReturnValue('0') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -258,10 +236,8 @@ describe('Facebook Conversions API Web - Init with User Data', () => { mockAnalytics.storage.get.mockReturnValue('0') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -282,10 +258,8 @@ describe('Facebook Conversions API Web - Init with User Data', () => { mockAnalytics.storage.get.mockReturnValue('0') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -306,10 +280,8 @@ describe('Facebook Conversions API Web - Init with User Data', () => { mockAnalytics.storage.get.mockReturnValue('0') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -330,10 +302,8 @@ describe('Facebook Conversions API Web - Init with User Data', () => { mockAnalytics.storage.get.mockReturnValue('0') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -345,24 +315,18 @@ describe('Facebook Conversions API Web - Init with User Data', () => { await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) // FBP and FBC should be trimmed but NOT hashed - expect(mockFbq).toHaveBeenCalledWith( - 'init', - 'test-pixel-123', - { - fbp: 'fb.1.1234567890.1234567890', - fbc: 'fb.1.1234567890.AbCdEf123' - } - ) + expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', { + fbp: 'fb.1.1234567890.1234567890', + fbc: 'fb.1.1234567890.AbCdEf123' + }) }) it('should format userData with all fields combined', async () => { mockAnalytics.storage.get.mockReturnValue('0') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -385,35 +349,29 @@ describe('Facebook Conversions API Web - Init with User Data', () => { await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) // All PII fields should be normalized and hashed; fbc/fbp should NOT be hashed - expect(mockFbq).toHaveBeenCalledWith( - 'init', - 'test-pixel-123', - { - external_id: 'fcdec6df4d44dbc637c7c5b58efface52a7f8a88535423430255be0bb89bedd8', - em: '973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b', - ph: '3c95277da5fd0da6a1a44ee3fdf56d20af6c6d242695a40e18e6e90dc3c5872c', - fn: '96d9632f363564cc3032521409cf22a852f2032eec099ed5967c0d000cec607a', - ln: '799ef92a11af918e3fb741df42934f3b568ed2d93ac1df74f1b8d41a27932a6f', - ge: '62c66a7a5dd70c3146618063c344e531e6d4b59e379808443ce962b3abd63c5a', - db: '53058fbd6731774c37a6d838c09d25b337fa7b9b5007f82cc934d857d2596e0c', - ct: '1a6bd4d9d79dc0a79b53795c70d3349fa9e38968a3fbefbfe8783efb1d2b6aac', - st: '6959097001d10501ac7d54c0bdb8db61420f658f2922cc26e46d536119a31126', - zp: '8137c19c8f35f6b6a1cce99753226e1c7211eaaebd68528b789f973b0be95e31', - country: '79adb2a2fce5c6ba215fe5f27f532d4e7edbac4b6a5e09e1ef3a08084a904621', - fbp: 'fb.1.1234567890.1234567890', - fbc: 'fb.1.1234567890.AbCdEf123' - } - ) + expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', { + external_id: 'fcdec6df4d44dbc637c7c5b58efface52a7f8a88535423430255be0bb89bedd8', + em: '973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b', + ph: '3c95277da5fd0da6a1a44ee3fdf56d20af6c6d242695a40e18e6e90dc3c5872c', + fn: '96d9632f363564cc3032521409cf22a852f2032eec099ed5967c0d000cec607a', + ln: '799ef92a11af918e3fb741df42934f3b568ed2d93ac1df74f1b8d41a27932a6f', + ge: '62c66a7a5dd70c3146618063c344e531e6d4b59e379808443ce962b3abd63c5a', + db: '53058fbd6731774c37a6d838c09d25b337fa7b9b5007f82cc934d857d2596e0c', + ct: '1a6bd4d9d79dc0a79b53795c70d3349fa9e38968a3fbefbfe8783efb1d2b6aac', + st: '6959097001d10501ac7d54c0bdb8db61420f658f2922cc26e46d536119a31126', + zp: '8137c19c8f35f6b6a1cce99753226e1c7211eaaebd68528b789f973b0be95e31', + country: '79adb2a2fce5c6ba215fe5f27f532d4e7edbac4b6a5e09e1ef3a08084a904621', + fbp: 'fb.1.1234567890.1234567890', + fbc: 'fb.1.1234567890.AbCdEf123' + }) }) it('should skip invalid gender values', async () => { mockAnalytics.storage.get.mockReturnValue('0') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -424,7 +382,7 @@ describe('Facebook Conversions API Web - Init with User Data', () => { await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) // Should not call init if only invalid gender is provided - const initCalls = (mockFbq).mock.calls.filter((call) => call[0] === 'init') + const initCalls = mockFbq.mock.calls.filter((call) => call[0] === 'init') expect(initCalls.length).toBe(0) }) @@ -432,10 +390,8 @@ describe('Facebook Conversions API Web - Init with User Data', () => { mockAnalytics.storage.get.mockReturnValue('0') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -446,7 +402,7 @@ describe('Facebook Conversions API Web - Init with User Data', () => { await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) // Should not call init if only invalid date is provided - const initCalls = (mockFbq).mock.calls.filter((call) => call[0] === 'init') + const initCalls = mockFbq.mock.calls.filter((call) => call[0] === 'init') expect(initCalls.length).toBe(0) }) }) @@ -456,10 +412,8 @@ describe('Facebook Conversions API Web - Init with User Data', () => { mockAnalytics.storage.get.mockReturnValue('2') const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -470,7 +424,7 @@ describe('Facebook Conversions API Web - Init with User Data', () => { await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) // Should not call init with user data when count is at max - const initCalls = (mockFbq).mock.calls.filter((call) => call[0] === 'init') + const initCalls = mockFbq.mock.calls.filter((call) => call[0] === 'init') expect(initCalls.length).toBe(0) }) @@ -490,10 +444,8 @@ describe('Facebook Conversions API Web - Init with User Data', () => { }) const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -504,42 +456,40 @@ describe('Facebook Conversions API Web - Init with User Data', () => { // First track event - init should be called (count: 0 -> 1) await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) - let initCalls = (mockFbq).mock.calls.filter((call) => call[0] === 'init') + let initCalls = mockFbq.mock.calls.filter((call) => call[0] === 'init') expect(initCalls.length).toBe(1) expect(currentInitCount).toBe(1) // Second track event - init should be called again (count: 1 -> 2) await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) - initCalls = (mockFbq).mock.calls.filter((call) => call[0] === 'init') + initCalls = mockFbq.mock.calls.filter((call) => call[0] === 'init') expect(initCalls.length).toBe(2) expect(currentInitCount).toBe(2) // Third track event - init should NOT be called (count stays at 2) await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) - initCalls = (mockFbq).mock.calls.filter((call) => call[0] === 'init') + initCalls = mockFbq.mock.calls.filter((call) => call[0] === 'init') expect(initCalls.length).toBe(2) // Still only 2 init calls expect(currentInitCount).toBe(2) // Count remains at max // Fourth track event - init should still NOT be called await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) - initCalls = (mockFbq).mock.calls.filter((call) => call[0] === 'init') + initCalls = mockFbq.mock.calls.filter((call) => call[0] === 'init') expect(initCalls.length).toBe(2) // Still only 2 init calls expect(currentInitCount).toBe(2) // Verify user data storage was still updated on all calls - const userDataSetCalls = (mockAnalytics.storage.set).mock.calls.filter((call) => call[0] === 'fb_user_data') + const userDataSetCalls = mockAnalytics.storage.set.mock.calls.filter((call) => call[0] === 'fb_user_data') expect(userDataSetCalls.length).toBe(4) // User data stored on all 4 track events }) it('should allow init to fire when count is reset to 0 (simulating new page load)', async () => { const payload = { - event_config: { - event_name: 'Purchase', - show_fields: false - }, + event_name: 'Purchase', + show_fields: false, content_ids: ['product-123'], value: 99.99, userData: { @@ -553,7 +503,7 @@ describe('Facebook Conversions API Web - Init with User Data', () => { await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) - let initCalls = (mockFbq).mock.calls.filter((call) => call[0] === 'init') + let initCalls = mockFbq.mock.calls.filter((call) => call[0] === 'init') expect(initCalls.length).toBe(0) // Init blocked when count at max // === New Page Load Scenario === @@ -565,11 +515,15 @@ describe('Facebook Conversions API Web - Init with User Data', () => { // First track event on new page await send(mockFbq, mockClientParamBuilder, payload, defaultSettings, mockAnalytics) - initCalls = (mockFbq).mock.calls.filter((call) => call[0] === 'init') + initCalls = mockFbq.mock.calls.filter((call) => call[0] === 'init') expect(initCalls.length).toBe(1) // Init fires when count is reset - expect(mockFbq).toHaveBeenCalledWith('init', 'test-pixel-123', expect.objectContaining({ - em: expect.any(String) - })) + expect(mockFbq).toHaveBeenCalledWith( + 'init', + 'test-pixel-123', + expect.objectContaining({ + em: expect.any(String) + }) + ) // Verify init count was incremented in storage expect(mockAnalytics.storage.set).toHaveBeenCalledWith('fb_pixel_init_count', '1') diff --git a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/depends-on.ts b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/depends-on.ts index 1b4f769aee4..5f8524c3c83 100644 --- a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/depends-on.ts +++ b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/depends-on.ts @@ -2,51 +2,81 @@ import { DependsOnConditions, Condition } from '@segment/actions-core/destinatio import type { FBStandardEventType, FBNonStandardEventType } from '../types' export const fieldDependencies: Record = { - custom_event_name: ['CustomEvent'], - content_category: ['PageView', 'ViewContent', 'Search'], - content_ids: ['AddPaymentInfo','AddToCart','AddToWishlist','InitiateCheckout','Purchase','Search','ViewContent'], - content_name: ['PageView', 'ViewContent', 'Search'], - content_type: ['AddToCart', 'Purchase', 'Search', 'ViewContent'], - contents: ['AddPaymentInfo', 'AddToCart', 'AddToWishlist', 'InitiateCheckout', 'Purchase', 'Search', 'ViewContent'], - currency: ['AddPaymentInfo', 'AddToCart', 'AddToWishlist', 'CompleteRegistration', 'InitiateCheckout', 'Lead', 'Purchase', 'Search', 'StartTrial', 'Subscribe', 'ViewContent'], - delivery_category: ['Purchase', 'InitiateCheckout'], - num_items: ['InitiateCheckout'], - predicted_ltv: ['Purchase', 'Subscribe', 'StartTrial', 'CompleteRegistration', 'AddPaymentInfo', 'CustomEvent' ], - net_revenue: ['Purchase'], - search_string: ['Search'], - status: ['CompleteRegistration'], - value: ['AddPaymentInfo', 'AddToCart', 'AddToWishlist', 'CompleteRegistration', 'InitiateCheckout', 'Lead', 'Purchase', 'Search', 'StartTrial', 'Subscribe', 'ViewContent'], + custom_event_name: ['CustomEvent'], + content_ids: [ + 'AddPaymentInfo', + 'AddToCart', + 'AddToWishlist', + 'InitiateCheckout', + 'Purchase', + 'Search', + 'ViewContent' + ], + content_type: ['AddToCart', 'Purchase', 'Search', 'ViewContent'], + contents: ['AddPaymentInfo', 'AddToCart', 'AddToWishlist', 'InitiateCheckout', 'Purchase', 'Search', 'ViewContent'], + currency: [ + 'AddPaymentInfo', + 'AddToCart', + 'AddToWishlist', + 'CompleteRegistration', + 'InitiateCheckout', + 'Lead', + 'Purchase', + 'Search', + 'StartTrial', + 'Subscribe', + 'ViewContent' + ], + delivery_category: ['Purchase', 'InitiateCheckout'], + num_items: ['InitiateCheckout', 'Purchase'], + predicted_ltv: ['Purchase', 'Subscribe', 'StartTrial', 'CompleteRegistration', 'AddPaymentInfo', 'CustomEvent'], + net_revenue: ['Purchase'], + search_string: ['Search'], + status: ['CompleteRegistration'], + value: [ + 'AddPaymentInfo', + 'AddToCart', + 'AddToWishlist', + 'CompleteRegistration', + 'InitiateCheckout', + 'Lead', + 'Purchase', + 'Search', + 'StartTrial', + 'Subscribe', + 'ViewContent' + ] } export function getDependenciesFor(fieldName: string): DependsOnConditions { - const conditions: Condition[] = [ - { - fieldKey: 'event_config.show_fields', - operator: 'is', - value: 'true' - } - ] - - const dependencies = fieldDependencies[fieldName] - - if(Array.isArray(dependencies) && dependencies.length > 1) { - dependencies.forEach(dep => { - conditions.push({ - fieldKey: 'event_config.event_name', - operator: 'is', - value: dep - }) - }) - } - - return { - match: 'any', - conditions + const conditions: Condition[] = [ + { + fieldKey: 'show_fields', + operator: 'is', + value: true } + ] + + const dependencies = fieldDependencies[fieldName] + + if (Array.isArray(dependencies) && dependencies.length >= 1) { + dependencies.forEach((dep) => { + conditions.push({ + fieldKey: 'event_name', + operator: 'is', + value: dep + }) + }) + } + + return { + match: 'any', + conditions + } } export function getNotVisibleForEvent(event: FBStandardEventType | FBNonStandardEventType): string[] { - return Object.entries(fieldDependencies) - .filter(([_, events]) => !events.includes(event)) - .map(([fieldName]) => fieldName) -} \ No newline at end of file + return Object.entries(fieldDependencies) + .filter(([_, events]) => !events.includes(event)) + .map(([fieldName]) => fieldName) +} diff --git a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/fields.ts b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/fields.ts index b6e2ce5196d..ec442427464 100644 --- a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/fields.ts +++ b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/fields.ts @@ -2,367 +2,377 @@ import type { InputField } from '@segment/actions-core' import { getDependenciesFor } from './depends-on' import { CURRENCY_ISO_CODES } from '../constants' -export const event_config: InputField = { - label: 'Event Configuration', - description: 'Specify the type of Facebook Conversions API event to send.', - type: 'object', - required: true, - additionalProperties: false, - defaultObjectUI: 'keyvalue', - properties: { - event_name: { - label: 'Event Name', - description: "Facebook Conversions API Event Name to send. Select 'Custom Event' to send a non standard event.", - type: 'string', - required: true, - choices: [ - { label: 'Custom Event', value: 'CustomEvent' }, - { label: 'Page View', value: 'PageView' }, - { label: 'Add Payment Info', value: 'AddPaymentInfo' }, - { label: 'Add To Cart', value: 'AddToCart' }, - { label: 'Add To Wishlist', value: 'AddToWishlist' }, - { label: 'Complete Registration', value: 'CompleteRegistration' }, - { label: 'Contact', value: 'Contact' }, - { label: 'Customize Product', value: 'CustomizeProduct' }, - { label: 'Donate', value: 'Donate' }, - { label: 'Find Location', value: 'FindLocation' }, - { label: 'Initiate Checkout', value: 'InitiateCheckout' }, - { label: 'Lead', value: 'Lead' }, - { label: 'Purchase', value: 'Purchase' }, - { label: 'Schedule', value: 'Schedule' }, - { label: 'Search', value: 'Search' }, - { label: 'Start Trial', value: 'StartTrial' }, - { label: 'Submit Application', value: 'SubmitApplication' }, - { label: 'Subscribe', value: 'Subscribe' }, - { label: 'View Content', value: 'ViewContent' } - ] - }, - custom_event_name:{ - label: 'Custom Event Name', - description: 'Custom event name to send to Facebook', - type: 'string', - depends_on: getDependenciesFor('custom_event_name') - }, - show_fields: { - label: 'Show all fields', - description: 'Show all fields, even those which are not relevant to the selected Event Name.', - type: 'boolean', - default: false - } - }, - default: { - event_name: 'CustomEvent', - custom_event_name: {'@path': '$.event'}, - show_fields: false - } +export const event_name: InputField = { + label: 'Event Name', + description: "Facebook Conversions API Event Name to send. Select 'Custom Event' to send a non standard event.", + type: 'string', + required: true, + choices: [ + { label: 'Custom Event', value: 'CustomEvent' }, + { label: 'Page View', value: 'PageView' }, + { label: 'Add Payment Info', value: 'AddPaymentInfo' }, + { label: 'Add To Cart', value: 'AddToCart' }, + { label: 'Add To Wishlist', value: 'AddToWishlist' }, + { label: 'Complete Registration', value: 'CompleteRegistration' }, + { label: 'Contact', value: 'Contact' }, + { label: 'Customize Product', value: 'CustomizeProduct' }, + { label: 'Donate', value: 'Donate' }, + { label: 'Find Location', value: 'FindLocation' }, + { label: 'Initiate Checkout', value: 'InitiateCheckout' }, + { label: 'Lead', value: 'Lead' }, + { label: 'Purchase', value: 'Purchase' }, + { label: 'Schedule', value: 'Schedule' }, + { label: 'Search', value: 'Search' }, + { label: 'Start Trial', value: 'StartTrial' }, + { label: 'Submit Application', value: 'SubmitApplication' }, + { label: 'Subscribe', value: 'Subscribe' }, + { label: 'View Content', value: 'ViewContent' } + ], + default: 'CustomEvent' +} + +export const custom_event_name: InputField = { + label: 'Custom Event Name', + description: 'Custom event name to send to Facebook', + type: 'string', + depends_on: getDependenciesFor('custom_event_name'), + required: { + match: 'all', + conditions: [{ fieldKey: 'event_name', operator: 'is', value: 'CustomEvent' }] + }, + default: { '@path': '$.event' } +} + +export const show_fields: InputField = { + label: 'Show all fields', + description: 'Show all fields, even those which are not relevant to the selected Event Name.', + type: 'boolean', + default: false } export const content_category: InputField = { - label: 'Content Category', - description: 'The category of the content associated with the event.', - type: 'string', - default: { '@path': '$.properties.category' }, - depends_on: getDependenciesFor('content_category') + label: 'Content Category', + description: 'The category of the content associated with the event.', + type: 'string', + default: { '@path': '$.properties.category' } } export const content_name: InputField = { - label: 'Content Name', - description: 'The name of the page or product associated with the event.', - type: 'string', - default: { '@path': '$.properties.name' }, - depends_on: getDependenciesFor('content_name') + label: 'Content Name', + description: 'The name of the page or product associated with the event.', + type: 'string', + default: { '@path': '$.properties.name' } } export const content_type: InputField = { - label: 'Content Type', - description: - 'If the IDs being passed in content_ids or contents parameter are IDs of products, then the value should be product. If product group IDs are being passed, then the value should be product_group. If no content_type is provided, Meta will match the event to every item that has the same ID, independent of its type.', - type: 'string', - choices: [ - { value: 'product', label: 'Product' }, - { value: 'product_group', label: 'Product Group' } - ], - default: 'product', - depends_on: getDependenciesFor('content_type') + label: 'Content Type', + description: + 'If the IDs being passed in content_ids or contents parameter are IDs of products, then the value should be product. If product group IDs are being passed, then the value should be product_group. If no content_type is provided, Meta will match the event to every item that has the same ID, independent of its type.', + type: 'string', + choices: [ + { value: 'product', label: 'Product' }, + { value: 'product_group', label: 'Product Group' } + ], + default: 'product', + depends_on: getDependenciesFor('content_type') } export const content_ids: InputField = { - label: 'Content IDs', - description: "Product IDs associated with the event, such as SKUs (e.g. ['ABC123', 'XYZ789']). Accepts a single string value or array of strings.", - type: 'string', - multiple: true, - default: { "@liquid": "{{ properties.products | map: 'product_id' }}"} - , - depends_on: getDependenciesFor('content_ids') + label: 'Content IDs', + description: + "Product IDs associated with the event, such as SKUs (e.g. ['ABC123', 'XYZ789']). Accepts a single string value or array of strings.", + type: 'string', + multiple: true, + depends_on: getDependenciesFor('content_ids') } export const contents: InputField = { - label: 'Contents', - description: 'A list of JSON objects that contain the product IDs associated with the event plus information about the products. ID and quantity are required fields.', - type: 'object', - multiple: true, - additionalProperties: true, - defaultObjectUI: 'keyvalue', - properties: { - id: { - label: 'ID', - description: 'The product ID of the purchased item.', - type: 'string', - required: true - }, - quantity: { - label: 'Quantity', - description: 'The number of items purchased.', - type: 'integer', - required: true - }, - item_price: { - label: 'Item Price', - description: 'The price of the item.', - type: 'number' - } + label: 'Contents', + description: + 'A list of JSON objects that contain the product IDs associated with the event plus information about the products. ID and quantity are required fields.', + type: 'object', + multiple: true, + additionalProperties: true, + defaultObjectUI: 'keyvalue', + properties: { + id: { + label: 'ID', + description: 'The product ID of the purchased item.', + type: 'string', + required: true }, - default: { - '@arrayPath': [ - '$.properties.products', - { - id: { '@path': '$.product_id' }, - quantity: { '@path': '$.quantity' }, - item_price: { '@path': '$.price' } - } - ] + quantity: { + label: 'Quantity', + description: 'The number of items purchased.', + type: 'integer', + required: true }, - depends_on: getDependenciesFor('contents') -} + item_price: { + label: 'Item Price', + description: 'The price of the item.', + type: 'number' + } + }, + default: { + '@arrayPath': [ + '$.properties.products', + { + id: { '@path': '$.product_id' }, + quantity: { '@path': '$.quantity' }, + item_price: { '@path': '$.price' } + } + ] + }, + depends_on: getDependenciesFor('contents') +} export const currency: InputField = { - label: 'Currency', - description: 'The currency for the value specified. Currency must be a valid ISO 4217 three-digit currency code.', - type: 'string', - default: { '@path': '$.properties.currency' }, - depends_on: getDependenciesFor('currency'), - choices:(() => { - return [...CURRENCY_ISO_CODES].map(code => ({ - value: code, - label: code - })) - })(), - required: { - match: 'all', - conditions: [ - { - fieldKey: 'event_config.event_name', - operator: 'is', - value: 'Purchase' - } - ] - } + label: 'Currency', + description: 'The currency for the value specified. Currency must be a valid ISO 4217 three-digit currency code.', + type: 'string', + default: { '@path': '$.properties.currency' }, + depends_on: getDependenciesFor('currency'), + choices: (() => { + return [...CURRENCY_ISO_CODES].map((code) => ({ + value: code, + label: code + })) + })(), + required: { + match: 'all', + conditions: [ + { + fieldKey: 'event_name', + operator: 'is', + value: 'Purchase' + } + ] + } } export const delivery_category: InputField = { - label: 'Delivery Category', - description: 'Category of the delivery', - type: 'string', - choices: [ - { value: 'in_store', label: 'In Store' }, - { value: 'curbside', label: 'Curbside' }, - { value: 'home_delivery', label: 'Home Delivery' } - ], - depends_on: getDependenciesFor('delivery_category') + label: 'Delivery Category', + description: 'Category of the delivery', + type: 'string', + choices: [ + { value: 'in_store', label: 'In Store' }, + { value: 'curbside', label: 'Curbside' }, + { value: 'home_delivery', label: 'Home Delivery' } + ], + depends_on: getDependenciesFor('delivery_category') } export const num_items: InputField = { - label: 'Number of Items', - description: 'The number of items when checkout was initiated.', - type: 'integer', - default: { '@path': '$.properties.num_items' }, - depends_on: getDependenciesFor('num_items') + label: 'Number of Items', + description: 'The number of items when checkout was initiated.', + type: 'integer', + minimum: 0, + default: { '@path': '$.properties.num_items' }, + depends_on: getDependenciesFor('num_items') } export const predicted_ltv: InputField = { - label: 'Predicted LTV', - description: 'Predicted lifetime value of a subscriber as defined by the advertiser and expressed as an exact value.', - type: 'number', - depends_on: getDependenciesFor('predicted_ltv') + label: 'Predicted LTV', + description: 'Predicted lifetime value of a subscriber as defined by the advertiser and expressed as an exact value.', + type: 'number', + minimum: 0, + depends_on: getDependenciesFor('predicted_ltv') } export const net_revenue: InputField = { - label: 'Net Revenue', - description: 'The net revenue associated with the purchase.', - type: 'number', - default: { '@path': '$.properties.net_revenue' }, - depends_on: getDependenciesFor('net_revenue') + label: 'Net Revenue', + description: 'The net revenue associated with the purchase.', + type: 'number', + minimum: 0, + default: { '@path': '$.properties.net_revenue' }, + depends_on: getDependenciesFor('net_revenue') } export const search_string: InputField = { - label: 'Search String', - description: 'The string entered by the user for the search.', - type: 'string', - default: { '@path': '$.properties.query' }, - depends_on: getDependenciesFor('search_string') + label: 'Search String', + description: 'The string entered by the user for the search.', + type: 'string', + default: { '@path': '$.properties.query' }, + depends_on: getDependenciesFor('search_string') } export const status: InputField = { - label: 'Registration Status', - description: 'The status of the registration. true for completed registrations, false otherwise.', - type: 'boolean', - depends_on: getDependenciesFor('status') + label: 'Registration Status', + description: 'The status of the registration. true for completed registrations, false otherwise.', + type: 'boolean', + depends_on: getDependenciesFor('status') } export const value: InputField = { - label: 'Value', - description: 'A numeric value associated with this event. This could be a monetary value or a value in some other metric.', - type: 'number', - default: { '@path': '$.properties.value' }, - depends_on: getDependenciesFor('value'), - required: { - match: 'all', - conditions: [ - { - fieldKey: 'event_config.event_name', - operator: 'is', - value: 'Purchase' - } - ] - } + label: 'Value', + description: + 'A numeric value associated with this event. This could be a monetary value or a value in some other metric.', + type: 'number', + minimum: 0, + default: { '@path': '$.properties.value' }, + depends_on: getDependenciesFor('value'), + required: { + match: 'all', + conditions: [ + { + fieldKey: 'event_name', + operator: 'is', + value: 'Purchase' + } + ] + } } export const custom_data: InputField = { - label: 'Custom Data', - description: 'The custom data object can be used to pass custom properties.', - type: 'object', - additionalProperties: true, - defaultObjectUI: 'keyvalue' + label: 'Custom Data', + description: 'The custom data object can be used to pass custom properties.', + type: 'object', + additionalProperties: true, + defaultObjectUI: 'keyvalue' } export const eventID: InputField = { - label: 'Event ID', - description: 'This ID can be any unique string. Event ID is used to deduplicate events sent both the server side Conversions API and the browser Pixel.', - type: 'string', - default: { '@path': '$.messageId' } + label: 'Event ID', + description: + 'This ID can be any unique string. Event ID is used to deduplicate events sent both the server side Conversions API and the browser Pixel.', + type: 'string', + default: { '@path': '$.messageId' } } export const eventSourceUrl: InputField = { - label: 'Event Source URL', - description: 'The URL of the page where the event occurred. Can be used to override the default URL taken from the current page.', - type: 'string', - default: { '@path': '$.context.page.url' } + label: 'Event Source URL', + description: + 'The URL of the page where the event occurred. Can be used to override the default URL taken from the current page.', + type: 'string', + default: { '@path': '$.context.page.url' } } export const userData: InputField = { - label: 'User Data', - description: 'User data to be sent with the event. This can include hashed identifiers like email, phone number, etc.', - type: 'object', - additionalProperties: false, - defaultObjectUI: 'keyvalue', - properties: { - external_id: { - label: 'External ID', - description: 'A unique identifier for the user from your system', - type: 'string' - }, - em: { - label: 'Email', - description: 'Email address of the user', - type: 'string', - format: 'email' - }, - ph: { - label: 'Phone Number', - description: 'Phone number of the user. Make sure to include the country code. For example, "15551234567" for a US number.', - type: 'string' - }, - fn: { - label: 'First Name', - description: 'First name of the user', - type: 'string' - }, - ln: { - label: 'Last Name', - description: 'Last name of the user', - type: 'string' - }, - ge: { - label: 'Gender', - description: 'Gender of the user. If unknown leave blank.', - type: 'string', - choices: [ - { label: 'Male', value: 'm' }, - { label: 'Female', value: 'f' } - ] - }, - db: { - label: 'Date of Birth', - description: 'Date of birth of the user', - type: 'string', - format: 'date' - }, - ct: { - label: 'City', - description: 'City of the user', - type: 'string' - }, - st: { - label: 'State', - description: 'State of the user. Facebook expects the 2-letter abbreviation for US states. For example, "CA" for California, or "NY" for New York.', - type: 'string' - }, - zp: { - label: 'ZIP/Postal Code', - description: 'ZIP or postal code of the user. For example, U.S zip code: 94035, Australia zip code: 1987, France zip code: 75018, UK zip code: m11ae.', - type: 'string' - }, - country: { - label: 'Country', - description: 'The country of the user. Facebook expects the 2-letter ISO 3166-1 alpha-2 country code. For example, "US" for the United States, or "GB" for the United Kingdom.', - type: 'string' - }, - fbp: { - label: 'FBP', - description: 'Use this field to pass the Facebook browser cookie value (_fbp) associated with the user. If the "Format User Data with Parameter Builder" setting is enabled, Segment will automatically capture this value from the _fbp cookie.', - type: 'string' - }, - fbc: { - label: 'FBC', - description: 'Use this field to pass The Facebook browser cookie value (_fbc) associated with the user. If the "Format User Data with Parameter Builder" setting is enabled, Segment will automatically capture this value from the _fbc cookie.', - type: 'string' - } + label: 'User Data', + description: + 'User data to be sent with the event. This can include hashed identifiers like email, phone number, etc.', + type: 'object', + additionalProperties: false, + defaultObjectUI: 'keyvalue', + properties: { + external_id: { + label: 'External ID', + description: 'A unique identifier for the user from your system', + type: 'string' + }, + em: { + label: 'Email', + description: 'Email address of the user', + type: 'string', + format: 'email' }, - default: { - external_id: { '@path': '$.userId' }, - em: { '@path': '$.context.traits.email' }, - ph: { '@path': '$.context.traits.phone' }, - fn: { '@path': '$.context.traits.first_name' }, - ln: { '@path': '$.context.traits.last_name' }, - ge: { '@path': '$.context.traits.gender' }, - db: { '@path': '$.context.traits.birthday' }, - ct: { '@path': '$.context.traits.address.city' }, - st: { '@path': '$.context.traits.address.state' }, - zp: { '@path': '$.context.traits.address.postal_code' }, - country: { '@path': '$.context.traits.address.country' }, - fbp: { '@path': '$.context.traits.fbp' }, - fbc: { '@path': '$.context.traits.fbc' }, - client_ip_address: { '@path': '$.context.traits.client_ip_address' } + ph: { + label: 'Phone Number', + description: + 'Phone number of the user. Make sure to include the country code. For example, "15551234567" for a US number.', + type: 'string' + }, + fn: { + label: 'First Name', + description: 'First name of the user', + type: 'string' + }, + ln: { + label: 'Last Name', + description: 'Last name of the user', + type: 'string' + }, + ge: { + label: 'Gender', + description: + 'Gender of the user. acceptable values are "m" or "male" for male and "f" or "female" for female. If unknown leave blank.', + type: 'string', + choices: [ + { label: 'm', value: 'm' }, + { label: 'f', value: 'f' }, + { label: 'male', value: 'male' }, + { label: 'female', value: 'female' } + ] + }, + db: { + label: 'Date of Birth', + description: 'Date of birth of the user', + type: 'string', + format: 'date' + }, + ct: { + label: 'City', + description: 'City of the user', + type: 'string' + }, + st: { + label: 'State', + description: + 'State of the user. Facebook expects the 2-letter abbreviation for US states. For example, "CA" for California, or "NY" for New York.', + type: 'string' + }, + zp: { + label: 'ZIP/Postal Code', + description: + 'ZIP or postal code of the user. For example, U.S zip code: 94035, Australia zip code: 1987, France zip code: 75018, UK zip code: m11ae.', + type: 'string' + }, + country: { + label: 'Country', + description: + 'The country of the user. Facebook expects the 2-letter ISO 3166-1 alpha-2 country code. For example, "US" for the United States, or "GB" for the United Kingdom.', + type: 'string' + }, + fbp: { + label: 'FBP', + description: + 'Use this field to pass the Facebook browser cookie value (_fbp) associated with the user. If the "Format User Data with Parameter Builder" setting is enabled, Segment will automatically capture this value from the _fbp cookie.', + type: 'string' + }, + fbc: { + label: 'FBC', + description: + 'Use this field to pass The Facebook browser cookie value (_fbc) associated with the user. If the "Format User Data with Parameter Builder" setting is enabled, Segment will automatically capture this value from the _fbc cookie.', + type: 'string' } + }, + default: { + external_id: { '@path': '$.userId' }, + em: { '@path': '$.context.traits.email' }, + ph: { '@path': '$.context.traits.phone' }, + fn: { '@path': '$.context.traits.first_name' }, + ln: { '@path': '$.context.traits.last_name' }, + ge: { '@path': '$.context.traits.gender' }, + db: { '@path': '$.context.traits.birthday' }, + ct: { '@path': '$.context.traits.address.city' }, + st: { '@path': '$.context.traits.address.state' }, + zp: { '@path': '$.context.traits.address.postal_code' }, + country: { '@path': '$.context.traits.address.country' }, + fbp: { '@path': '$.context.traits.fbp' }, + fbc: { '@path': '$.context.traits.fbc' }, + client_ip_address: { '@path': '$.context.traits.client_ip_address' } + } } export const AllFields = { - event_config, - content_category, - content_ids, - content_name, - content_type, - contents, - currency, - delivery_category, - num_items, - predicted_ltv, - net_revenue, - search_string, - status, - value, - custom_data, - eventID, - eventSourceUrl, - userData -} \ No newline at end of file + event_name, + custom_event_name, + show_fields, + content_category, + content_ids, + content_name, + content_type, + contents, + currency, + delivery_category, + num_items, + predicted_ltv, + net_revenue, + search_string, + status, + value, + custom_data, + eventID, + eventSourceUrl, + userData +} diff --git a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/functions.ts b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/functions.ts index 93b3458a6ce..e5ff46692b4 100644 --- a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/functions.ts +++ b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/functions.ts @@ -1,292 +1,331 @@ -import { FBEvent, UserData, EventOptions, FBClient, FBStandardEventType, FBNonStandardEventType, FBClientParamBuilder, PIIType, PIIParamName } from '../types' +import { + FBEvent, + UserData, + EventOptions, + FBClient, + FBStandardEventType, + FBNonStandardEventType, + FBClientParamBuilder, + PIIType, + PIIParamName +} from '../types' import { Payload } from './generated-types' import { Settings } from '../generated-types' import { UniversalStorage, Analytics } from '@segment/analytics-next' -import {US_STATE_CODES, COUNTRY_CODES, MAX_INIT_COUNT, INIT_COUNT_KEY, USER_DATA_KEY } from '../constants' +import { + US_STATE_CODES, + COUNTRY_CODES, + MAX_INIT_COUNT, + INIT_COUNT_KEY, + USER_DATA_KEY, + CURRENCY_ISO_CODES +} from '../constants' import { storageFallback, setStorageInitCount } from '../functions' import { getNotVisibleForEvent } from './depends-on' -export async function send(client: FBClient, clientParamBuilder: FBClientParamBuilder | undefined, payload: Payload, settings: Settings, analytics: Analytics) { - const { pixelId } = settings - const { - event_config: { - custom_event_name, - event_name - } = {} - } = payload +export async function send( + client: FBClient, + clientParamBuilder: FBClientParamBuilder | undefined, + payload: Payload, + settings: Settings, + analytics: Analytics +) { + const { pixelId } = settings + const { custom_event_name, event_name } = payload - const isCustom = event_name === 'CustomEvent' ? true : false + const isCustom = event_name === 'CustomEvent' ? true : false - const errorMessage = validate(payload) + const errorMessage = validate(payload) - if(errorMessage) { - console.warn(`${errorMessage}`) - return - } - - const fbEvent = formatFBEvent(payload) - - await maybeSendUserData(client, clientParamBuilder, payload, settings, analytics) - - const options = formatOptions(payload) - - if(isCustom){ - client( - 'trackSingleCustom', - pixelId, - custom_event_name as string, - { ...fbEvent }, - options - ) - } - else { - client( - 'trackSingle', - pixelId, - event_name as FBStandardEventType, - { ...fbEvent }, - options - ) - } + if (errorMessage) { + console.warn(`${errorMessage}`) + return + } + + const fbEvent = formatFBEvent(payload) + + await maybeSendUserData(client, clientParamBuilder, payload, settings, analytics) + + const options = formatOptions(payload) + + if (isCustom) { + client('trackSingleCustom', pixelId, custom_event_name as string, { ...fbEvent }, options) + } else { + client('trackSingle', pixelId, event_name as FBStandardEventType, { ...fbEvent }, options) + } } function validate(payload: Payload): string | undefined { - const { - event_config: { event_name }, - content_ids, - contents - } = payload - - if(['AddToCart', 'Purchase', 'ViewContent'].includes(event_name)){ - if ((!content_ids || (Array.isArray(content_ids) && content_ids.length === 0)) && (!contents || (Array.isArray(contents) && contents.length === 0))) { - return `At least one of content_ids or contents is required for the ${event_name} event.` - } + const { event_name, custom_event_name, content_ids, contents } = payload + + if (event_name === 'CustomEvent' && !trimmed(custom_event_name)) { + return `custom_event_name is required when event_name is CustomEvent.` + } + + if (['AddToCart', 'Purchase', 'ViewContent'].includes(event_name)) { + const contentIds = trimmedArray(toArray(content_ids)) + const contentsArr = trimContents(toArray(contents)) + if (contentIds.length === 0 && contentsArr.length === 0) { + return `At least one of content_ids or contents is required for the ${event_name} event.` } + } - return undefined + return undefined } -export function formatFBEvent(payload: Payload): FBEvent { - const { - content_category, - content_ids, - content_name, - content_type, - contents, - currency, - delivery_category, - num_items, - value, - predicted_ltv, - net_revenue, - custom_data, - event_config: { - event_name, - show_fields - } = {} - } = payload - - const fbEvent: FBEvent = { - partner_agent: 'segment', - ...(content_category ? { content_category } : {}), - ...(content_ids && Array.isArray(content_ids) && content_ids.length > 0 ? { content_ids } : {}), - ...(content_name ? { content_name } : {}), - ...(content_type ? { content_type } : {}), - ...(contents && Array.isArray(contents) && contents.length > 0 ? { contents } : {}), - ...(currency ? { currency } : {}), - ...(delivery_category ? { delivery_category } : {}), - ...(typeof num_items === 'number' ? { num_items } : {}), - ...(typeof value === 'number' ? { value } : {}), - ...(typeof predicted_ltv === 'number' ? { predicted_ltv } : {}), - ...(typeof net_revenue === 'number' ? { net_revenue } : {}), - ...(custom_data && Object.entries(custom_data).length > 0 ? { custom_data } : {}) - } +function toArray(value: T | T[] | undefined | null): T[] { + if (value === undefined || value === null) return [] + return Array.isArray(value) ? value : [value] +} - if(show_fields === false){ - // If show_fields is false we delete values for fields which are hidden in the UI. - const fieldsToDelete = getNotVisibleForEvent(event_name as FBStandardEventType | FBNonStandardEventType) - fieldsToDelete.forEach(field => { - if (field in fbEvent) { - delete fbEvent[field as keyof typeof fbEvent] - } - }) +function trimmed(value: unknown): string | undefined { + if (typeof value !== 'string') return undefined + const t = value.trim() + return t.length > 0 ? t : undefined +} + +function trimmedArray(values: string[]): string[] { + return values.map((v) => trimmed(v)).filter((v): v is string => v !== undefined) +} + +function normalizeCurrency(value: unknown): string | undefined { + const t = trimmed(value) + if (!t) return undefined + const upper = t.toUpperCase() + return CURRENCY_ISO_CODES.has(upper) ? upper : undefined +} + +function trimContents(contents: Payload['contents']): NonNullable { + return (contents ?? []).map((item) => { + if (!item || typeof item !== 'object') return item + const trimmedItem = { ...item } + for (const key of Object.keys(trimmedItem)) { + const v = trimmedItem[key] + if (typeof v === 'string') { + trimmedItem[key] = v.trim() + } } + return trimmedItem + }) +} - return fbEvent +export function formatFBEvent(payload: Payload): FBEvent { + const { + content_category, + content_ids, + content_name, + content_type, + contents, + currency, + delivery_category, + num_items, + search_string, + status, + value, + predicted_ltv, + net_revenue, + custom_data, + event_name, + show_fields + } = payload + + const contentIdsArr = trimmedArray(toArray(content_ids)) + const contentsArr = trimContents(toArray(contents)) + const contentCategory = trimmed(content_category) + const contentName = trimmed(content_name) + const contentType = trimmed(content_type) + const deliveryCategory = trimmed(delivery_category) + const searchString = trimmed(search_string) + const currencyCode = normalizeCurrency(currency) + + const fbEvent: FBEvent = { + partner_agent: 'segment', + ...(contentCategory ? { content_category: contentCategory } : {}), + ...(contentIdsArr.length > 0 ? { content_ids: contentIdsArr } : {}), + ...(contentName ? { content_name: contentName } : {}), + ...(contentType ? { content_type: contentType } : {}), + ...(contentsArr.length > 0 ? { contents: contentsArr } : {}), + ...(currencyCode ? { currency: currencyCode } : {}), + ...(deliveryCategory ? { delivery_category: deliveryCategory } : {}), + ...(typeof num_items === 'number' ? { num_items } : {}), + ...(searchString ? { search_string: searchString } : {}), + ...(typeof status === 'boolean' ? { status } : {}), + ...(typeof value === 'number' ? { value } : {}), + ...(typeof predicted_ltv === 'number' ? { predicted_ltv } : {}), + ...(typeof net_revenue === 'number' ? { net_revenue } : {}), + ...(custom_data && Object.entries(custom_data).length > 0 ? { custom_data } : {}) + } + + if (show_fields === false) { + // If show_fields is false we delete values for fields which are hidden in the UI. + const fieldsToDelete = getNotVisibleForEvent(event_name as FBStandardEventType | FBNonStandardEventType) + fieldsToDelete.forEach((field) => { + if (field in fbEvent) { + delete fbEvent[field as keyof typeof fbEvent] + } + }) + } + + return fbEvent } function formatOptions(payload: Payload): EventOptions | undefined { - const { eventID, eventSourceUrl } = payload - const options: EventOptions = { - ...(eventID ? { eventID } : {}), - ...(eventSourceUrl ? { eventSourceUrl } : {}), - } - return Object.values(options).some(Boolean) ? options : undefined + const { eventID, eventSourceUrl } = payload + const options: EventOptions = { + ...(eventID ? { eventID } : {}), + ...(eventSourceUrl ? { eventSourceUrl } : {}) + } + return Object.values(options).some(Boolean) ? options : undefined } -async function maybeSendUserData(client: FBClient, clientParamBuilder: FBClientParamBuilder | undefined, payload: Payload, settings: Settings, analytics: Analytics) { - const { - pixelId - } = settings - const { - userData - } = payload - const userDataFormatted = await formatUserData(userData, clientParamBuilder) - - if(userDataFormatted) { - /* - Facebook indicated that init should only trigger on a single page load up to max 2 times. - When userData is created it gets added to storage and included in the next init call on page load. - Facebook also advised to always send userData when it's available, even if it was collected via previous events. - */ - const storage = (analytics.storage as UniversalStorage>) ?? storageFallback - const initCountFromStorage: string | null = storage.get(INIT_COUNT_KEY) - const initCount: number | undefined = (initCountFromStorage && !isNaN(Number(initCountFromStorage))) - ? parseInt(initCountFromStorage, 10) - : undefined - - if(typeof initCount === 'number' && initCount < MAX_INIT_COUNT) { - client('init', pixelId, userDataFormatted) - setStorageInitCount(analytics, initCount + 1) - } - - storage.set(USER_DATA_KEY, JSON.stringify(userDataFormatted)) - } -} +async function maybeSendUserData( + client: FBClient, + clientParamBuilder: FBClientParamBuilder | undefined, + payload: Payload, + settings: Settings, + analytics: Analytics +) { + const { pixelId } = settings + const { userData } = payload + const userDataFormatted = await formatUserData(userData, clientParamBuilder) -export async function formatUserData(userData: Payload['userData'], clientParamBuilder: FBClientParamBuilder | undefined): Promise { - if(!userData){ - return undefined - } + if (userDataFormatted) { + /* + Facebook indicated that init should only trigger on a single page load up to max 2 times. + When userData is created it gets added to storage and included in the next init call on page load. + Facebook also advised to always send userData when it's available, even if it was collected via previous events. + */ + const storage = (analytics.storage as UniversalStorage>) ?? storageFallback + const initCountFromStorage: string | null = storage.get(INIT_COUNT_KEY) + const initCount: number | undefined = + initCountFromStorage && !isNaN(Number(initCountFromStorage)) ? parseInt(initCountFromStorage, 10) : undefined - const { - external_id, - em, - ph, - fn, - ln, - ge, - db, - ct, - st, - zp, - country, - fbp, - fbc - } = userData - - let fbcValue = fbc ? fbc.trim() : undefined - let fbpValue = fbp ? fbp.trim() : undefined - - if(clientParamBuilder){ - clientParamBuilder.processAndCollectAllParams() - fbcValue = clientParamBuilder.getFbc() || fbcValue - fbpValue = clientParamBuilder.getFbp() || fbpValue + if (typeof initCount === 'number' && initCount < MAX_INIT_COUNT) { + client('init', pixelId, userDataFormatted) + setStorageInitCount(analytics, initCount + 1) } - const [ - emData, - phData, - fnData, - lnData, - geData, - dbData, - ctData, - stData, - zpData, - countryData, - externalIdData - ] = await Promise.all([ - formatPII(em, 'email', 'em', clientParamBuilder, (s) => s.toLowerCase().trim()), - formatPII(ph, 'phone', 'ph', clientParamBuilder, (s) => s.replace(/\D/g, '')), - formatPII(fn, 'first_name', 'fn', clientParamBuilder, (s) => s.toLowerCase().trim()), - formatPII(ln, 'last_name', 'ln', clientParamBuilder, (s) => s.toLowerCase().trim()), - formatPII(ge, 'gender', 'ge', clientParamBuilder, (s) => (['m', 'f'].includes(s) ? s : undefined)), - formatPII(db, 'date_of_birth', 'db', clientParamBuilder, (s) => formatDate(s)), - formatPII(ct, 'city', 'ct', clientParamBuilder, (s) => s.toLowerCase().replace(/\s+/g, '')), - formatPII(st, 'state', 'st', clientParamBuilder, (s) => fromMap(US_STATE_CODES, s)), - formatPII(zp, 'zip_code', 'zp', clientParamBuilder, (s) => s.trim()), - formatPII(country, 'country', 'country', clientParamBuilder, (s) => fromMap(COUNTRY_CODES, s)), - formatPII(external_id, 'external_id', 'external_id', clientParamBuilder, (s) => s.trim()) - ]) + storage.set(USER_DATA_KEY, JSON.stringify(userDataFormatted)) + } +} - const ud: UserData = { - ...emData, - ...phData, - ...fnData, - ...lnData, - ...(geData as { ge?: 'm' | 'f' }), - ...dbData, - ...ctData, - ...stData, - ...zpData, - ...countryData, - ...externalIdData, - ...(fbcValue ? { fbc: fbcValue } : {}), - ...(fbpValue ? { fbp: fbpValue } : {}) - } +export async function formatUserData( + userData: Payload['userData'], + clientParamBuilder: FBClientParamBuilder | undefined +): Promise { + if (!userData) { + return undefined + } + + const { external_id, em, ph, fn, ln, ge, db, ct, st, zp, country, fbp, fbc } = userData - if(Object.keys(ud).length === 0){ + let fbcValue = fbc ? fbc.trim() : undefined + let fbpValue = fbp ? fbp.trim() : undefined + + if (clientParamBuilder) { + clientParamBuilder.processAndCollectAllParams() + fbcValue = clientParamBuilder.getFbc() || fbcValue + fbpValue = clientParamBuilder.getFbp() || fbpValue + } + + const [emData, phData, fnData, lnData, geData, dbData, ctData, stData, zpData, countryData, externalIdData] = + await Promise.all([ + formatPII(em, 'email', 'em', clientParamBuilder, (s) => s.toLowerCase().trim()), + formatPII(ph, 'phone', 'ph', clientParamBuilder, (s) => s.replace(/\D/g, '')), + formatPII(fn, 'first_name', 'fn', clientParamBuilder, (s) => s.toLowerCase().trim()), + formatPII(ln, 'last_name', 'ln', clientParamBuilder, (s) => s.toLowerCase().trim()), + formatPII(ge, 'gender', 'ge', clientParamBuilder, (s) => { + const g = s.toLowerCase().trim() + if (g === 'm' || g === 'male') return 'm' + if (g === 'f' || g === 'female') return 'f' return undefined - } - return ud + }), + formatPII(db, 'date_of_birth', 'db', clientParamBuilder, (s) => formatDate(s)), + formatPII(ct, 'city', 'ct', clientParamBuilder, (s) => s.toLowerCase().replace(/\s+/g, '')), + formatPII(st, 'state', 'st', clientParamBuilder, (s) => fromMap(US_STATE_CODES, s)), + formatPII(zp, 'zip_code', 'zp', clientParamBuilder, (s) => s.trim()), + formatPII(country, 'country', 'country', clientParamBuilder, (s) => fromMap(COUNTRY_CODES, s)), + formatPII(external_id, 'external_id', 'external_id', clientParamBuilder, (s) => s.trim()) + ]) + + const ud: UserData = { + ...emData, + ...phData, + ...fnData, + ...lnData, + ...(geData as { ge?: 'm' | 'f' }), + ...dbData, + ...ctData, + ...stData, + ...zpData, + ...countryData, + ...externalIdData, + ...(fbcValue ? { fbc: fbcValue } : {}), + ...(fbpValue ? { fbp: fbpValue } : {}) + } + + if (Object.keys(ud).length === 0) { + return undefined + } + return ud } async function formatPII( - value: string | undefined, - piiType: PIIType, - piiParamType: K, - clientParamBuilder: FBClientParamBuilder | undefined, - formatter: (s: string) => string | undefined -): Promise>> { - if(!value) { - return {} + value: string | undefined, + piiType: PIIType, + piiParamType: K, + clientParamBuilder: FBClientParamBuilder | undefined, + formatter: (s: string) => string | undefined +): Promise>> { + if (!value) { + return {} + } + if (clientParamBuilder) { + const val = clientParamBuilder.getNormalizedAndHashedPII(value, piiType) + return val ? ({ [piiParamType]: val as V } as Partial>) : {} + } else { + const val = formatter(value) + if (!val) { + return {} + } else { + const hashValue = await sha256Hash(val) + return { [piiParamType]: hashValue as V } as Partial> } - if(clientParamBuilder){ - const val = clientParamBuilder.getNormalizedAndHashedPII(value, piiType) - return val ? ({ [piiParamType]: val as V } as Partial>) : {} - } - else { - const val = formatter(value) - if(!val) { - return {} - } - else { - const hashValue = await sha256Hash(val) - return ({ [piiParamType]: hashValue as V }) as Partial> - } - } + } } function formatDate(isoDate?: string): string | undefined { - if (!isoDate || typeof isoDate !== 'string') { - return undefined - } - const date = new Date(isoDate) - if (isNaN(date.getTime())) { - return undefined - } - const year = date.getUTCFullYear() - const month = (date.getUTCMonth() + 1).toString().padStart(2, '0') - const day = date.getUTCDate().toString().padStart(2, '0') - return `${year}${month}${day}` + if (!isoDate || typeof isoDate !== 'string') { + return undefined + } + const date = new Date(isoDate) + if (isNaN(date.getTime())) { + return undefined + } + const year = date.getUTCFullYear() + const month = (date.getUTCMonth() + 1).toString().padStart(2, '0') + const day = date.getUTCDate().toString().padStart(2, '0') + return `${year}${month}${day}` } function fromMap(map: Map, value?: string): string | undefined { - const cleaned = value?.toLowerCase().replace(/[^a-z]/g, '').trim() - if (!cleaned) { - return undefined - } - if(cleaned.length === 2 && Array.from(map.values()).includes(cleaned)) { - return cleaned - } - return map.get(cleaned) || undefined + const cleaned = value + ?.toLowerCase() + .replace(/[^a-z]/g, '') + .trim() + if (!cleaned) { + return undefined + } + if (cleaned.length === 2 && Array.from(map.values()).includes(cleaned)) { + return cleaned + } + return map.get(cleaned) || undefined } export async function sha256Hash(value: string): Promise { - const encoder = new TextEncoder() - const data = encoder.encode(value) - const hashBuffer = await window.crypto.subtle.digest('SHA-256', data) - const hashArray = Array.from(new Uint8Array(hashBuffer)) - return hashArray.map(b => b.toString(16).padStart(2, '0')).join('') -} \ No newline at end of file + const encoder = new TextEncoder() + const data = encoder.encode(value) + const hashBuffer = await window.crypto.subtle.digest('SHA-256', data) + const hashArray = Array.from(new Uint8Array(hashBuffer)) + return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') +} diff --git a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/generated-types.ts b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/generated-types.ts index 7106763353d..227db3f5b50 100644 --- a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/generated-types.ts +++ b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/send/generated-types.ts @@ -2,22 +2,17 @@ export interface Payload { /** - * Specify the type of Facebook Conversions API event to send. + * Facebook Conversions API Event Name to send. Select 'Custom Event' to send a non standard event. */ - event_config: { - /** - * Facebook Conversions API Event Name to send. Select 'Custom Event' to send a non standard event. - */ - event_name: string - /** - * Custom event name to send to Facebook - */ - custom_event_name?: string - /** - * Show all fields, even those which are not relevant to the selected Event Name. - */ - show_fields?: boolean - } + event_name: string + /** + * Custom event name to send to Facebook + */ + custom_event_name?: string + /** + * Show all fields, even those which are not relevant to the selected Event Name. + */ + show_fields?: boolean /** * The category of the content associated with the event. */ @@ -123,7 +118,7 @@ export interface Payload { */ ln?: string /** - * Gender of the user. If unknown leave blank. + * Gender of the user. acceptable values are "m" or "male" for male and "f" or "female" for female. If unknown leave blank. */ ge?: string /** diff --git a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/types.ts b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/types.ts index 43fdc42cb06..41952189b6b 100644 --- a/packages/browser-destinations/destinations/facebook-conversions-api-web/src/types.ts +++ b/packages/browser-destinations/destinations/facebook-conversions-api-web/src/types.ts @@ -1,6 +1,6 @@ export interface WindowWithOptionalFbq extends Omit { - fbq?: FBClient; - _fbq?: FBClient; + fbq?: FBClient + _fbq?: FBClient } export type FBStandardEventType = @@ -29,8 +29,8 @@ export type InitOptions = { agent?: string } -export type EventOptions = { - eventID?: string +export type EventOptions = { + eventID?: string eventSourceUrl?: string } @@ -66,7 +66,9 @@ export type FBEvent = { currency?: string delivery_category?: string num_items?: number - value?: number + search_string?: string + status?: boolean + value?: number custom_data?: { [k: string]: unknown } @@ -81,36 +83,49 @@ export type FBClient = { callMethod?: (...args: unknown[]) => void (command: 'set', key: string, value: boolean, pixelId: string): void (command: 'dataProcessingOptions', options: string[], country?: number, state?: number): void - (command: 'init', pixelId: string, userData?: UserData, options?: InitOptions ): void + (command: 'init', pixelId: string, userData?: UserData, options?: InitOptions): void (command: 'trackSingle', pixelId: string, event: FBStandardEventType, params?: FBEvent, options?: EventOptions): void (command: 'trackSingleCustom', pixelId: string, event: string, params?: FBEvent, options?: EventOptions): void } export type FBClientParamBuilder = { getNormalizedAndHashedPII: (value: string, piiType: PIIType) => string | undefined - processAndCollectAllParams: () => void + processAndCollectAllParams: () => void getFbc: () => string | undefined getFbp: () => string | undefined } -export type PIIType = 'email' | 'phone' | 'first_name' | 'last_name' | 'gender' | 'date_of_birth' | 'city' | 'state' | 'zip_code' | 'country' | 'external_id' +export type PIIType = + | 'email' + | 'phone' + | 'first_name' + | 'last_name' + | 'gender' + | 'date_of_birth' + | 'city' + | 'state' + | 'zip_code' + | 'country' + | 'external_id' export type PIIParamName = 'em' | 'ph' | 'fn' | 'ln' | 'ge' | 'db' | 'ct' | 'st' | 'zp' | 'country' | 'external_id' export const LDU = { - Disabled: {key: 'Disabled', state: undefined, country: undefined}, - GeolocationLogic: {key: 'GeolocationLogic', state: 0, country: 0}, - California: {key: 'California', state: 1000, country: 1}, - Colorado: {key: 'Colorado', state: 1001, country: 1}, - Connecticut: {key: 'Connecticut', state: 1002, country: 1}, - Florida: {key: 'Florida', state: 1003, country: 1}, - Oregon: {key: 'Oregon', state: 1004, country: 1}, - Texas: {key: 'Texas', state: 1005, country: 1}, - Montana: {key: 'Montana', state: 1006, country: 1}, - Delaware: {key: 'Delaware', state: 1007, country: 1}, - Nebraska: {key: 'Nebraska', state: 1008, country: 1}, - NewHampshire: {key: 'NewHampshire', state: 1009, country: 1}, - NewJersey: {key: 'NewJersey', state: 1010, country: 1}, - Minnesota: {key: 'Minnesota', state: 1011, country: 1} + Disabled: { key: 'Disabled', state: undefined, country: undefined }, + GeolocationLogic: { key: 'GeolocationLogic', state: 0, country: 0 }, + California: { key: 'California', state: 1000, country: 1 }, + Colorado: { key: 'Colorado', state: 1001, country: 1 }, + Connecticut: { key: 'Connecticut', state: 1002, country: 1 }, + Florida: { key: 'Florida', state: 1003, country: 1 }, + Oregon: { key: 'Oregon', state: 1004, country: 1 }, + Texas: { key: 'Texas', state: 1005, country: 1 }, + Montana: { key: 'Montana', state: 1006, country: 1 }, + Delaware: { key: 'Delaware', state: 1007, country: 1 }, + Nebraska: { key: 'Nebraska', state: 1008, country: 1 }, + NewHampshire: { key: 'NewHampshire', state: 1009, country: 1 }, + NewJersey: { key: 'NewJersey', state: 1010, country: 1 }, + Minnesota: { key: 'Minnesota', state: 1011, country: 1 }, + Maryland: { key: 'Maryland', state: 1012, country: 1 }, + RhodeIsland: { key: 'RhodeIsland', state: 1013, country: 1 } } as const -export type LDU = typeof LDU[keyof typeof LDU] \ No newline at end of file +export type LDU = typeof LDU[keyof typeof LDU]