diff --git a/integrations/appboy/lib/index.js b/integrations/appboy/lib/index.js index bf861e7af..17e10539e 100644 --- a/integrations/appboy/lib/index.js +++ b/integrations/appboy/lib/index.js @@ -407,19 +407,9 @@ Appboy.prototype.orderCompleted = function(track) { del(purchaseProperties, 'currency'); // we have to make a separate call to appboy for each product - each(function(product) { - var track = new Track({ properties: product }); - var productId = track.productId(); - var price = track.price(); - var quantity = track.quantity(); - window.appboy.logPurchase( - productId, - price, - currencyCode, - quantity, - purchaseProperties - ); - }, products); + for (var i = 0; i < products.length; i++) { + logProduct(products[i], currencyCode, purchaseProperties); + } }; /** @@ -449,3 +439,92 @@ function getGender(gender) { if (otherGenders.indexOf(gender.toLowerCase()) > -1) return window.appboy.ab.User.Genders.OTHER; } + +/** + * Logs a Purchase containing a product as described in Braze's documentation: + * https://js.appboycdn.com/web-sdk/latest/doc/module-appboy.html#.logPurchase + * + * @param {Object} product Product from the Order Completed call + * @param {String} currencyCode Currency code + * @param {Object} extraProperties Root properties from the track call + */ +function logProduct(product, currencyCode, extraProperties) { + var track = new Track({ properties: product }); + var productId = track.productId(); + var price = track.price(); + var quantity = track.quantity(); + var productProperties = track.properties(); + var properties = {}; + + del(productProperties, 'productId'); + del(productProperties, 'price'); + del(productProperties, 'quantity'); + + for (var productProperty in productProperties) { + if (!productProperties.hasOwnProperty(productProperty)) { + continue; + } + + var value = productProperties[productProperty]; + if (isValidProperty(productProperty, value)) { + properties[productProperty] = value; + } + } + + for (var property in extraProperties) { + if (!extraProperties.hasOwnProperty(property)) { + continue; + } + + var val = extraProperties[property]; + if ( + !productProperties.hasOwnProperty(property) && + isValidProperty(property, val) + ) { + properties[property] = val; + } + } + + window.appboy.logPurchase( + productId, + price, + currencyCode, + quantity, + properties + ); +} + +/** + * Validates a name and value of a property, following Braze's restrictions: + * + * Names are limited to 255 characters in length, cannot begin with a $, and + * can only contain alphanumeric characters and punctuation. Values can be + * numeric, boolean, Date objects, or strings 255 characters or shorter. + * + * @param {String} name Name of the property. + * @param {*} value Value of the property. + * + * @return {boolean} true if the name and value are valid, false otherwise. + */ +function isValidProperty(name, value) { + if (name.length > 255 || name.startsWith('$')) { + return false; + } + + if (typeof value === 'number' || typeof value === 'boolean') { + return true; + } + + if (typeof value === 'object' && value instanceof Date) { + return true; + } + + if ( + (typeof value === 'string' || value instanceof String) && + value.length <= 255 + ) { + return true; + } + + return false; +} diff --git a/integrations/appboy/package.json b/integrations/appboy/package.json index fb6323da6..c5d13c9b8 100644 --- a/integrations/appboy/package.json +++ b/integrations/appboy/package.json @@ -1,7 +1,7 @@ { "name": "@segment/analytics.js-integration-appboy", "description": "The Appboy analytics.js integration.", - "version": "1.9.0", + "version": "1.10.0", "keywords": [ "analytics.js", "analytics.js-integration", diff --git a/integrations/appboy/test/index.test.js b/integrations/appboy/test/index.test.js index 97cc4beee..eeeee9770 100644 --- a/integrations/appboy/test/index.test.js +++ b/integrations/appboy/test/index.test.js @@ -440,11 +440,15 @@ describe('Appboy', function() { }); it('should call logPurchase for each product in a Completed Order event', function() { + var today = new Date(); + analytics.track('Order Completed', { total: 30, revenue: 25, shipping: 3, currency: 'USD', + date: today, + invalidPropertyLength: 'a'.repeat(500), products: [ { product_id: '507f1f77bcf86cd799439011', @@ -452,7 +456,9 @@ describe('Appboy', function() { name: 'Monopoly: 3rd Edition', price: 19.23, quantity: 1, - category: 'Games' + category: 'Games', + $invalidPropertyName: 3, + invalidPropertyValue: ['red', 'blue'] }, { product_id: '505bd76785ebb509fc183733', @@ -460,7 +466,8 @@ describe('Appboy', function() { name: 'Uno Card Game', price: 3, quantity: 2, - category: 'Games' + category: 'Games', + size: 6 } ] }); @@ -473,7 +480,11 @@ describe('Appboy', function() { { total: 30, revenue: 25, - shipping: 3 + shipping: 3, + sku: '45790-32', + name: 'Monopoly: 3rd Edition', + category: 'Games', + date: today } ); analytics.called( @@ -485,7 +496,12 @@ describe('Appboy', function() { { total: 30, revenue: 25, - shipping: 3 + shipping: 3, + sku: '46493-32', + name: 'Uno Card Game', + category: 'Games', + size: 6, + date: today } ); });