|
| 1 | +'use strict'; |
| 2 | + |
| 3 | +/** |
| 4 | + * Module dependencies. |
| 5 | + */ |
| 6 | + |
| 7 | +var convertDates = require('@segment/convert-dates'); |
| 8 | +var defaults = require('@ndhoule/defaults'); |
| 9 | +var del = require('obj-case').del; |
| 10 | +var integration = require('@segment/analytics.js-integration'); |
| 11 | +var is = require('is'); |
| 12 | +var extend = require('@ndhoule/extend'); |
| 13 | +var clone = require('@ndhoule/clone'); |
| 14 | +var each = require('@ndhoule/each'); |
| 15 | +var pick = require('@ndhoule/pick'); |
| 16 | + |
| 17 | +/** |
| 18 | + * Expose `Intercom` integration. |
| 19 | + */ |
| 20 | + |
| 21 | +var Intercom = module.exports = integration('Intercom') |
| 22 | + .global('Intercom') |
| 23 | + .option('activator', '#IntercomDefaultWidget') |
| 24 | + .option('appId', '') |
| 25 | + .option('richLinkProperties', []) |
| 26 | + .tag('<script src="https://widget.intercom.io/widget/{{ appId }}">'); |
| 27 | + |
| 28 | +/** |
| 29 | + * Initialize. |
| 30 | + * |
| 31 | + * http://docs.intercom.io/ |
| 32 | + * http://docs.intercom.io/#IntercomJS |
| 33 | + * |
| 34 | + * @api public |
| 35 | + */ |
| 36 | + |
| 37 | +Intercom.prototype.initialize = function() { |
| 38 | + // Shim out the Intercom library. |
| 39 | + window.Intercom = function() { |
| 40 | + window.Intercom.q.push(arguments); |
| 41 | + }; |
| 42 | + window.Intercom.q = []; |
| 43 | + |
| 44 | + this.load(this.ready); |
| 45 | +}; |
| 46 | + |
| 47 | +/** |
| 48 | + * Loaded? |
| 49 | + * |
| 50 | + * @api private |
| 51 | + * @return {boolean} |
| 52 | + */ |
| 53 | + |
| 54 | +Intercom.prototype.loaded = function() { |
| 55 | + return typeof window.Intercom === 'function'; |
| 56 | +}; |
| 57 | + |
| 58 | +/** |
| 59 | + * Page. |
| 60 | + * |
| 61 | + * @api public |
| 62 | + * @param {Page} page |
| 63 | + */ |
| 64 | + |
| 65 | +Intercom.prototype.page = function(page) { |
| 66 | + var integrationSettings = page.options(this.name); |
| 67 | + this.bootOrUpdate({}, integrationSettings); |
| 68 | +}; |
| 69 | + |
| 70 | +/** |
| 71 | + * Identify. |
| 72 | + * |
| 73 | + * http://docs.intercom.io/#IntercomJS |
| 74 | + * |
| 75 | + * @api public |
| 76 | + * @param {Identify} identify |
| 77 | + */ |
| 78 | + |
| 79 | +Intercom.prototype.identify = function(identify) { |
| 80 | + var traits = identify.traits({ userId: 'user_id' }); |
| 81 | + var integrationSettings = identify.options(this.name); |
| 82 | + var companyCreated = identify.companyCreated(); |
| 83 | + var created = identify.created(); |
| 84 | + var name = identify.name(); |
| 85 | + var id = identify.userId(); |
| 86 | + var group = this.analytics.group(); |
| 87 | + var settings = this.options; |
| 88 | + |
| 89 | + if (!id && !identify.email()) { |
| 90 | + return; |
| 91 | + } |
| 92 | + |
| 93 | + // intercom requires `company` to be an object. default it with group traits |
| 94 | + // so that we guarantee an `id` is there, since they require it |
| 95 | + if (traits.company !== null && !is.object(traits.company)) { |
| 96 | + delete traits.company; |
| 97 | + } |
| 98 | + |
| 99 | + if (traits.company) { |
| 100 | + defaults(traits.company, group.traits()); |
| 101 | + } |
| 102 | + |
| 103 | + // name |
| 104 | + if (name) traits.name = name; |
| 105 | + |
| 106 | + // handle dates |
| 107 | + if (created) { |
| 108 | + del(traits, 'created'); |
| 109 | + del(traits, 'createdAt'); |
| 110 | + traits.created_at = created; |
| 111 | + } |
| 112 | + |
| 113 | + if (companyCreated) { |
| 114 | + del(traits.company, 'created'); |
| 115 | + del(traits.company, 'createdAt'); |
| 116 | + traits.company.created_at = companyCreated; |
| 117 | + } |
| 118 | + |
| 119 | + // convert dates |
| 120 | + traits = convertDates(traits, formatDate); |
| 121 | + |
| 122 | + // format nested custom traits |
| 123 | + traits = formatNestedCustomTraits(traits, settings); |
| 124 | + |
| 125 | + // handle options |
| 126 | + if (integrationSettings.userHash) traits.user_hash = integrationSettings.userHash; |
| 127 | + if (integrationSettings.user_hash) traits.user_hash = integrationSettings.user_hash; |
| 128 | + |
| 129 | + this.bootOrUpdate(traits, integrationSettings); |
| 130 | +}; |
| 131 | + |
| 132 | +/** |
| 133 | + * Group. |
| 134 | + * |
| 135 | + * @api public |
| 136 | + * @param {Group} group |
| 137 | + */ |
| 138 | + |
| 139 | +Intercom.prototype.group = function(group) { |
| 140 | + var settings = this.options; |
| 141 | + // using .traits here since group.properties() doesn't take alias object |
| 142 | + var props = group.traits({ |
| 143 | + createdAt: 'created', |
| 144 | + created: 'created_at', |
| 145 | + monthlySpend: 'monthly_spend' |
| 146 | + }); |
| 147 | + props = convertDates(props, formatDate); |
| 148 | + var id = group.groupId(); |
| 149 | + if (id) props.id = id; |
| 150 | + var integrationSettings = group.options(this.name); |
| 151 | + |
| 152 | + // format nested custom traits |
| 153 | + props = formatNestedCustomTraits(props, settings); |
| 154 | + |
| 155 | + var traits = extend({ company: props }, hideDefaultLauncher(integrationSettings)); |
| 156 | + |
| 157 | + api('update', traits); |
| 158 | +}; |
| 159 | + |
| 160 | +/** |
| 161 | + * Track. |
| 162 | + * |
| 163 | + * @api public |
| 164 | + * @param {Track} track |
| 165 | + */ |
| 166 | + |
| 167 | +Intercom.prototype.track = function(track) { |
| 168 | + var settings = this.options; |
| 169 | + var props = track.properties(); |
| 170 | + var revenue = track.revenue(); |
| 171 | + if (revenue) { |
| 172 | + var revenueData = { |
| 173 | + // Intercom requests value in cents |
| 174 | + price: { |
| 175 | + amount: revenue * 100, |
| 176 | + currency: track.currency() // fallsback on 'USD' |
| 177 | + } |
| 178 | + }; |
| 179 | + } |
| 180 | + |
| 181 | + // format Nested custom traits |
| 182 | + props = formatNestedCustomTraits(props, settings); |
| 183 | + |
| 184 | + props = extend(props, revenueData); |
| 185 | + del(props, 'revenue'); |
| 186 | + del(props, 'currency'); |
| 187 | + |
| 188 | + api('trackEvent', track.event(), props); |
| 189 | +}; |
| 190 | + |
| 191 | +/** |
| 192 | + * Boots or updates, as appropriate. |
| 193 | + * |
| 194 | + * @api private |
| 195 | + * @param {Object} options |
| 196 | + */ |
| 197 | + |
| 198 | +Intercom.prototype.bootOrUpdate = function(options, integrationSettings) { |
| 199 | + options = options || {}; |
| 200 | + var method = this.booted === true ? 'update' : 'boot'; |
| 201 | + var activator = this.options.activator; |
| 202 | + options.app_id = this.options.appId; |
| 203 | + |
| 204 | + // Intercom, will force the widget to appear if the selector is |
| 205 | + // #IntercomDefaultWidget so no need to check inbox, just need to check that |
| 206 | + // the selector isn't #IntercomDefaultWidget. |
| 207 | + if (activator !== '#IntercomDefaultWidget') { |
| 208 | + options.widget = { activator: activator }; |
| 209 | + } |
| 210 | + // Check for selective showing of messenger option |
| 211 | + options = extend(options, hideDefaultLauncher(integrationSettings)); |
| 212 | + |
| 213 | + api(method, options); |
| 214 | + this.booted = true; |
| 215 | +}; |
| 216 | + |
| 217 | +/** |
| 218 | + * Format a date to Intercom's liking. |
| 219 | + * |
| 220 | + * @api private |
| 221 | + * @param {Date} date |
| 222 | + * @return {number} |
| 223 | + */ |
| 224 | + |
| 225 | +function formatDate(date) { |
| 226 | + return Math.floor(date / 1000); |
| 227 | +} |
| 228 | + |
| 229 | +/** |
| 230 | + * Flatten selectively based on your settings. You can either stringify, flatten, or drop the properties. |
| 231 | + * Intercom rejects nested objects so you must choose a method. |
| 232 | + * |
| 233 | + * @param {Object} obj |
| 234 | + * @param {Object} settings |
| 235 | + * @return {Object} ret |
| 236 | + * @api private |
| 237 | + */ |
| 238 | + |
| 239 | +function formatNestedCustomTraits(obj, settings) { |
| 240 | + var richLinkProperties = settings.richLinkProperties; |
| 241 | + var basicIntercomTraits = [ |
| 242 | + 'companies', |
| 243 | + 'company', |
| 244 | + 'created_at', |
| 245 | + 'created', |
| 246 | + 'custom_attributes', |
| 247 | + 'company_id', |
| 248 | + 'id', |
| 249 | + 'name', |
| 250 | + 'monthly_spend', |
| 251 | + 'plan', |
| 252 | + 'remote_created_at', |
| 253 | + 'remove', |
| 254 | + 'user_id', |
| 255 | + 'email' |
| 256 | + ]; |
| 257 | + |
| 258 | + // add rich link object to semantic traits so that it's not altered by the default method and |
| 259 | + // is passed to intercom as a nested object: https://developers.intercom.com/reference#event-metadata-types |
| 260 | + var semanticTraits = basicIntercomTraits.concat(richLinkProperties); |
| 261 | + |
| 262 | + // clone traits so we don't modify the original object |
| 263 | + var customTraits = clone(obj); |
| 264 | + |
| 265 | + // filter out semanticTraits so that we only format custom nested traits |
| 266 | + each(function(trait) { |
| 267 | + del(customTraits, trait); |
| 268 | + }, semanticTraits); |
| 269 | + |
| 270 | + // create object without custom traits to merge with formatted custom traits in the end |
| 271 | + var standardTraits = pick(semanticTraits, obj); |
| 272 | + |
| 273 | + // drop any arrays or objects |
| 274 | + var supportedTraits = {}; |
| 275 | + each(function(value, key) { |
| 276 | + if (!is.object(value) && !is.array(value)) supportedTraits[key] = value; |
| 277 | + }, customTraits); |
| 278 | + |
| 279 | + // combine all the traits |
| 280 | + return extend(supportedTraits, standardTraits); |
| 281 | +} |
| 282 | + |
| 283 | +/** |
| 284 | + * Push a call onto the Intercom queue. |
| 285 | + * |
| 286 | + * @api private |
| 287 | + */ |
| 288 | + |
| 289 | +function api() { |
| 290 | + window.Intercom.apply(window.Intercom, arguments); |
| 291 | +} |
| 292 | + |
| 293 | +/** |
| 294 | + * Selectively hide messenger |
| 295 | + * https://docs.intercom.io/configure-intercom-for-your-product-or-site/customize-the-intercom-messenger/customize-the-intercom-messenger-technical#show-the-intercom-messenger-to-selected-users-for-web- |
| 296 | + * @param {Object} options |
| 297 | + * @return {Object} ret |
| 298 | + * @api private |
| 299 | + */ |
| 300 | + |
| 301 | +function hideDefaultLauncher(options) { |
| 302 | + var ret = {}; |
| 303 | + var setting = options.hideDefaultLauncher; |
| 304 | + if (setting === undefined || typeof setting !== 'boolean') return ret; |
| 305 | + ret.hide_default_launcher= setting; |
| 306 | + return ret; |
| 307 | +} |
0 commit comments