modules/tracker.js

  1. /* eslint-disable complexity */
  2. /* eslint-disable max-len */
  3. /* eslint-disable object-curly-newline, no-underscore-dangle, camelcase, no-unneeded-ternary */
  4. const EventEmitter = require('../utils/events');
  5. const helpers = require('../utils/helpers');
  6. const RequestQueue = require('../utils/request-queue');
  7. function applyParams(parameters, options) {
  8. const {
  9. apiKey,
  10. version,
  11. sessionId,
  12. clientId,
  13. userId,
  14. segments,
  15. testCells,
  16. requestMethod,
  17. beaconMode,
  18. } = options;
  19. const { host, pathname } = helpers.getWindowLocation();
  20. const sendReferrerWithTrackingEvents = (options.sendReferrerWithTrackingEvents === false)
  21. ? false
  22. : true; // Defaults to 'true'
  23. let aggregateParams = Object.assign(parameters);
  24. if (version) {
  25. aggregateParams.c = version;
  26. }
  27. if (clientId) {
  28. aggregateParams.i = clientId;
  29. }
  30. if (sessionId) {
  31. aggregateParams.s = sessionId;
  32. }
  33. if (userId) {
  34. aggregateParams.ui = String(userId);
  35. }
  36. if (segments && segments.length) {
  37. aggregateParams.us = segments;
  38. }
  39. if (apiKey) {
  40. aggregateParams.key = apiKey;
  41. }
  42. if (testCells) {
  43. Object.keys(testCells).forEach((testCellKey) => {
  44. aggregateParams[`ef-${testCellKey}`] = testCells[testCellKey];
  45. });
  46. }
  47. if (beaconMode && requestMethod && requestMethod.match(/POST/i)) {
  48. aggregateParams.beacon = true;
  49. }
  50. if (sendReferrerWithTrackingEvents && host) {
  51. aggregateParams.origin_referrer = host;
  52. if (pathname) {
  53. aggregateParams.origin_referrer += pathname;
  54. }
  55. }
  56. aggregateParams._dt = Date.now();
  57. aggregateParams = helpers.cleanParams(aggregateParams);
  58. return aggregateParams;
  59. }
  60. // Append common parameters to supplied parameters object and return as string
  61. function applyParamsAsString(parameters, options) {
  62. return helpers.stringify(applyParams(parameters, options));
  63. }
  64. /**
  65. * Interface to tracking related API calls
  66. *
  67. * @module tracker
  68. * @inner
  69. * @returns {object}
  70. */
  71. class Tracker {
  72. constructor(options) {
  73. this.options = options || {};
  74. this.eventemitter = new EventEmitter();
  75. this.requests = new RequestQueue(options, this.eventemitter);
  76. }
  77. /**
  78. * Send session start event to API
  79. * @private
  80. * @function trackSessionStartV2
  81. * @param {object} [networkParameters] - Parameters relevant to the network request
  82. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  83. * @returns {(true|Error)}
  84. * @example
  85. * constructorio.tracker.trackSessionStartV2();
  86. */
  87. trackSessionStartV2(networkParameters = {}) {
  88. const url = `${this.options.serviceUrl}/v2/behavioral_action/session_start?`;
  89. this.requests.queue(`${url}${applyParamsAsString({}, this.options)}`, 'POST', undefined, networkParameters);
  90. this.requests.send();
  91. return true;
  92. }
  93. /**
  94. * Send session start event to API
  95. *
  96. * @function trackSessionStart
  97. * @param {object} [networkParameters] - Parameters relevant to the network request
  98. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  99. * @returns {(true|Error)}
  100. * @example
  101. * constructorio.tracker.trackSessionStart();
  102. */
  103. trackSessionStart(networkParameters = {}) {
  104. const url = `${this.options.serviceUrl}/behavior?`;
  105. const queryParams = { action: 'session_start' };
  106. this.requests.queue(`${url}${applyParamsAsString(queryParams, this.options)}`, undefined, undefined, networkParameters);
  107. this.requests.send();
  108. return true;
  109. }
  110. /**
  111. * Send input focus event to API
  112. * @private
  113. * @function trackInputFocusV2
  114. * @param {string} userInput - The current autocomplete search query
  115. * @param {object} parameters - Additional parameters to be sent with request
  116. * @param {object} [parameters.analyticsTags] - Pass additional analytics data
  117. * @param {object} [networkParameters] - Parameters relevant to the network request
  118. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  119. * @returns {(true|Error)}
  120. * @description User focused on search input element
  121. * @example
  122. * constructorio.tracker.trackInputFocusV2("text");
  123. */
  124. trackInputFocusV2(userInput = '', parameters = {}, networkParameters = {}) {
  125. const baseUrl = `${this.options.serviceUrl}/v2/behavioral_action/focus?`;
  126. const bodyParams = {};
  127. const {
  128. analyticsTags = null,
  129. } = parameters;
  130. if (analyticsTags) {
  131. bodyParams.analytics_tags = analyticsTags;
  132. }
  133. bodyParams.user_input = userInput;
  134. const requestMethod = 'POST';
  135. const requestBody = applyParams(bodyParams, {
  136. ...this.options,
  137. requestMethod,
  138. });
  139. this.requests.queue(`${baseUrl}${applyParamsAsString({}, this.options)}`, requestMethod, requestBody, networkParameters);
  140. this.requests.send();
  141. return true;
  142. }
  143. /**
  144. * Send input focus event to API
  145. *
  146. * @function trackInputFocus
  147. * @param {object} [networkParameters] - Parameters relevant to the network request
  148. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  149. * @returns {(true|Error)}
  150. * @description User focused on search input element
  151. * @example
  152. * constructorio.tracker.trackInputFocus();
  153. */
  154. trackInputFocus(networkParameters = {}) {
  155. const url = `${this.options.serviceUrl}/behavior?`;
  156. const queryParams = { action: 'focus' };
  157. this.requests.queue(`${url}${applyParamsAsString(queryParams, this.options)}`, undefined, undefined, networkParameters);
  158. this.requests.send();
  159. return true;
  160. }
  161. /**
  162. * Send item detail load event to API
  163. *
  164. * @function trackItemDetailLoad
  165. * @param {object} parameters - Additional parameters to be sent with request
  166. * @param {string} parameters.itemName - Product item name
  167. * @param {string} parameters.itemId - Product item unique identifier
  168. * @param {string} parameters.url - Current page URL
  169. * @param {object} [parameters.analyticsTags] - Pass additional analytics data
  170. * @param {string} [parameters.variationId] - Product item variation unique identifier
  171. * @param {object} [networkParameters] - Parameters relevant to the network request
  172. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  173. * @returns {(true|Error)}
  174. * @description User loaded an item detail page
  175. * @example
  176. * constructorio.tracker.trackItemDetailLoad(
  177. * {
  178. * itemName: 'Red T-Shirt',
  179. * itemId: 'KMH876',
  180. * url: 'https://constructor.io/product/KMH876',
  181. * },
  182. * );
  183. */
  184. trackItemDetailLoad(parameters, networkParameters = {}) {
  185. // Ensure parameters are provided (required)
  186. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  187. const requestUrlPath = `${this.options.serviceUrl}/v2/behavioral_action/item_detail_load?`;
  188. const queryParams = {};
  189. const bodyParams = {};
  190. const {
  191. item_name,
  192. name,
  193. item_id,
  194. customer_id,
  195. customerId = customer_id,
  196. variation_id,
  197. itemName = item_name || name,
  198. itemId = item_id || customerId,
  199. variationId = variation_id,
  200. url,
  201. analyticsTags,
  202. } = parameters;
  203. // Ensure support for both item_name and name as parameters
  204. if (itemName) {
  205. bodyParams.item_name = itemName;
  206. }
  207. // Ensure support for both item_id and customer_id as parameters
  208. if (itemId) {
  209. bodyParams.item_id = itemId;
  210. }
  211. if (variationId) {
  212. bodyParams.variation_id = variationId;
  213. }
  214. if (analyticsTags) {
  215. bodyParams.analytics_tags = analyticsTags;
  216. }
  217. if (url) {
  218. bodyParams.url = url;
  219. }
  220. const requestURL = `${requestUrlPath}${applyParamsAsString(queryParams, this.options)}`;
  221. const requestMethod = 'POST';
  222. const requestBody = applyParams(bodyParams, { ...this.options, requestMethod });
  223. this.requests.queue(requestURL, requestMethod, requestBody, networkParameters);
  224. this.requests.send();
  225. return true;
  226. }
  227. this.requests.send();
  228. return new Error('parameters are required of type object');
  229. }
  230. /**
  231. * Send autocomplete select event to API
  232. * @private
  233. * @function trackAutocompleteSelectV2
  234. * @param {string} itemName - Name of selected autocomplete item
  235. * @param {object} parameters - Additional parameters to be sent with request
  236. * @param {string} parameters.userInput - The current autocomplete search query
  237. * @param {string} [parameters.section] - Section the selected item resides within
  238. * @param {string} [parameters.tr] - Trigger used to select the item (click, etc.)
  239. * @param {string} [parameters.itemId] - Item id of the selected item
  240. * @param {string} [parameters.variationId] - Variation id of the selected item
  241. * @param {string} [parameters.groupId] - Group identifier of the group to search within. Only required if searching within a group, i.e. "Pumpkin in Canned Goods"
  242. * @param {object} [networkParameters] - Parameters relevant to the network request
  243. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  244. * @returns {(true|Error)}
  245. * @description User selected (clicked, or navigated to via keyboard) a result that appeared within autocomplete
  246. * @example
  247. * constructorio.tracker.trackAutocompleteSelectV2(
  248. * 'T-Shirt',
  249. * {
  250. * userInput: 'Shirt',
  251. * section: 'Products',
  252. * tr: 'click',
  253. * groupId: '88JU230',
  254. * itemId: '12345',
  255. * variationId: '12345-A',
  256. * },
  257. * );
  258. */
  259. trackAutocompleteSelectV2(itemName, parameters, networkParameters = {}) {
  260. // Ensure term is provided (required)
  261. if (itemName && typeof itemName === 'string') {
  262. // Ensure parameters are provided (required)
  263. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  264. const baseUrl = `${this.options.serviceUrl}/v2/behavioral_action/autocomplete_select?`;
  265. const {
  266. original_query,
  267. originalQuery = original_query,
  268. user_input,
  269. userInput = originalQuery || user_input,
  270. original_section,
  271. section = original_section,
  272. tr,
  273. group_id,
  274. groupId = group_id,
  275. item_id,
  276. itemId = item_id,
  277. variation_id,
  278. variationId = variation_id,
  279. } = parameters;
  280. const queryParams = {};
  281. const bodyParams = {
  282. user_input: userInput,
  283. tr,
  284. group_id: groupId,
  285. item_id: itemId,
  286. variation_id: variationId,
  287. item_name: itemName,
  288. section,
  289. };
  290. if (section) {
  291. queryParams.section = section;
  292. }
  293. const requestURL = `${baseUrl}${applyParamsAsString(queryParams, this.options)}`;
  294. const requestMethod = 'POST';
  295. const requestBody = applyParams(bodyParams, {
  296. ...this.options,
  297. requestMethod,
  298. });
  299. this.requests.queue(
  300. requestURL,
  301. requestMethod,
  302. requestBody,
  303. networkParameters,
  304. );
  305. this.requests.send();
  306. return true;
  307. }
  308. this.requests.send();
  309. return new Error('parameters are required of type object');
  310. }
  311. this.requests.send();
  312. return new Error('term is a required parameter of type string');
  313. }
  314. /**
  315. * Send autocomplete select event to API
  316. *
  317. * @function trackAutocompleteSelect
  318. * @param {string} term - Term of selected autocomplete item
  319. * @param {object} parameters - Additional parameters to be sent with request
  320. * @param {string} parameters.originalQuery - The current autocomplete search query
  321. * @param {string} parameters.section - Section the selected item resides within
  322. * @param {string} [parameters.tr] - Trigger used to select the item (click, etc.)
  323. * @param {string} [parameters.groupId] - Group identifier of the group to search within. Only required if searching within a group, i.e. "Pumpkin in Canned Goods"
  324. * @param {string} [parameters.displayName] - Display name of group of selected item
  325. * @param {string} [parameters.itemId] - Item id of the selected item
  326. * @param {object} [networkParameters] - Parameters relevant to the network request
  327. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  328. * @returns {(true|Error)}
  329. * @description User selected (clicked, or navigated to via keyboard) a result that appeared within autocomplete
  330. * @example
  331. * constructorio.tracker.trackAutocompleteSelect(
  332. * 'T-Shirt',
  333. * {
  334. * originalQuery: 'Shirt',
  335. * section: 'Products',
  336. * tr: 'click',
  337. * groupId: '88JU230',
  338. * displayName: 'apparel',
  339. * itemId: '12345',
  340. * },
  341. * );
  342. */
  343. trackAutocompleteSelect(term, parameters, networkParameters = {}) {
  344. // Ensure term is provided (required)
  345. if (term && typeof term === 'string') {
  346. // Ensure parameters are provided (required)
  347. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  348. const url = `${this.options.serviceUrl}/autocomplete/${helpers.encodeURIComponentRFC3986(helpers.trimNonBreakingSpaces(term))}/select?`;
  349. const queryParams = {};
  350. const {
  351. original_query,
  352. originalQuery = original_query,
  353. section,
  354. original_section,
  355. originalSection = original_section,
  356. tr,
  357. group_id,
  358. groupId = group_id,
  359. display_name,
  360. displayName = display_name,
  361. item_id,
  362. itemId = item_id,
  363. } = parameters;
  364. if (originalQuery) {
  365. queryParams.original_query = originalQuery;
  366. }
  367. if (tr) {
  368. queryParams.tr = tr;
  369. }
  370. if (originalSection || section) {
  371. queryParams.section = originalSection || section;
  372. }
  373. if (groupId) {
  374. queryParams.group = {
  375. group_id: groupId,
  376. display_name: displayName,
  377. };
  378. }
  379. if (itemId) {
  380. queryParams.item_id = itemId;
  381. }
  382. this.requests.queue(`${url}${applyParamsAsString(queryParams, this.options)}`, undefined, undefined, networkParameters);
  383. this.requests.send();
  384. return true;
  385. }
  386. this.requests.send();
  387. return new Error('parameters are required of type object');
  388. }
  389. this.requests.send();
  390. return new Error('term is a required parameter of type string');
  391. }
  392. /**
  393. * Send autocomplete search event to API
  394. * @private
  395. * @function trackSearchSubmitV2
  396. * @param {string} searchTerm - Term of submitted autocomplete event
  397. * @param {object} parameters - Additional parameters to be sent with request
  398. * @param {string} parameters.userInput - The current autocomplete search query
  399. * @param {string} [parameters.groupId] - Group identifier of the group to search within. Only required if searching within a group, i.e. "Pumpkin in Canned Goods"
  400. * @param {string} [parameters.section] - The section name for the item Ex. "Products"
  401. * @param {object} [parameters.analyticsTags] - Pass additional analytics data
  402. * @param {object} [networkParameters] - Parameters relevant to the network request
  403. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  404. * @returns {(true|Error)}
  405. * @description User submitted a search (pressing enter within input element, or clicking submit element)
  406. * @example
  407. * constructorio.tracker.trackSearchSubmitV2(
  408. * 'T-Shirt',
  409. * {
  410. * userInput: 'Shirt',
  411. * groupId: '88JU230',
  412. * },
  413. * );
  414. */
  415. trackSearchSubmitV2(searchTerm, parameters, networkParameters = {}) {
  416. // Ensure term is provided (required)
  417. if (searchTerm && typeof searchTerm === 'string') {
  418. // Ensure parameters are provided (required)
  419. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  420. const baseUrl = `${this.options.serviceUrl}/v2/behavioral_action/search_submit?`;
  421. const {
  422. original_query,
  423. originalQuery = original_query,
  424. user_input,
  425. userInput = originalQuery || user_input,
  426. group_id,
  427. groupId = group_id,
  428. section,
  429. analyticsTags = null,
  430. } = parameters;
  431. const queryParams = {};
  432. const bodyParams = {
  433. user_input: userInput,
  434. search_term: searchTerm,
  435. section,
  436. };
  437. if (groupId) {
  438. bodyParams.filters = { group_id: groupId };
  439. }
  440. if (section) {
  441. queryParams.section = section;
  442. }
  443. if (analyticsTags) {
  444. bodyParams.analytics_tags = analyticsTags;
  445. }
  446. const requestURL = `${baseUrl}${applyParamsAsString(queryParams, this.options)}`;
  447. const requestMethod = 'POST';
  448. const requestBody = applyParams(bodyParams, {
  449. ...this.options,
  450. requestMethod,
  451. });
  452. this.requests.queue(
  453. requestURL,
  454. requestMethod,
  455. requestBody,
  456. networkParameters,
  457. );
  458. this.requests.send();
  459. return true;
  460. }
  461. this.requests.send();
  462. return new Error('parameters are required of type object');
  463. }
  464. this.requests.send();
  465. return new Error('term is a required parameter of type string');
  466. }
  467. /**
  468. * Send autocomplete search event to API
  469. *
  470. * @function trackSearchSubmit
  471. * @param {string} term - Term of submitted autocomplete event
  472. * @param {object} parameters - Additional parameters to be sent with request
  473. * @param {string} parameters.originalQuery - The current autocomplete search query
  474. * @param {string} [parameters.groupId] - Group identifier of the group to search within. Only required if searching within a group, i.e. "Pumpkin in Canned Goods"
  475. * @param {string} [parameters.displayName] - Display name of group of selected item
  476. * @param {object} [networkParameters] - Parameters relevant to the network request
  477. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  478. * @returns {(true|Error)}
  479. * @description User submitted a search (pressing enter within input element, or clicking submit element)
  480. * @example
  481. * constructorio.tracker.trackSearchSubmit(
  482. * 'T-Shirt',
  483. * {
  484. * originalQuery: 'Shirt',
  485. * groupId: '88JU230',
  486. * displayName: 'apparel',
  487. * },
  488. * );
  489. */
  490. trackSearchSubmit(term, parameters, networkParameters = {}) {
  491. // Ensure term is provided (required)
  492. if (term && typeof term === 'string') {
  493. // Ensure parameters are provided (required)
  494. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  495. const url = `${this.options.serviceUrl}/autocomplete/${helpers.encodeURIComponentRFC3986(helpers.trimNonBreakingSpaces(term))}/search?`;
  496. const queryParams = {};
  497. const {
  498. original_query,
  499. originalQuery = original_query,
  500. group_id,
  501. groupId = group_id,
  502. display_name,
  503. displayName = display_name,
  504. } = parameters;
  505. if (originalQuery) {
  506. queryParams.original_query = originalQuery;
  507. }
  508. if (groupId) {
  509. queryParams.group = {
  510. group_id: groupId,
  511. display_name: displayName,
  512. };
  513. }
  514. this.requests.queue(`${url}${applyParamsAsString(queryParams, this.options)}`, undefined, undefined, networkParameters);
  515. this.requests.send();
  516. return true;
  517. }
  518. this.requests.send();
  519. return new Error('parameters are required of type object');
  520. }
  521. this.requests.send();
  522. return new Error('term is a required parameter of type string');
  523. }
  524. /**
  525. * Send search results loaded event to API
  526. *
  527. * @function trackSearchResultsLoaded
  528. * @param {string} searchTerm - Search results query term
  529. * @param {object} parameters - Additional parameters to be sent with request
  530. * @param {string} parameters.url - URL of the search results page
  531. * @param {object[]} parameters.items - List of product item unique identifiers in search results listing
  532. * @param {number} [parameters.resultCount] - Total number of results
  533. * @param {number} [parameters.resultPage] - Current page of search results
  534. * @param {string} [parameters.resultId] - Browse result identifier (returned in response from Constructor)
  535. * @param {object} [parameters.selectedFilters] - Selected filters
  536. * @param {string} [parameters.sortOrder] - Sort order ('ascending' or 'descending')
  537. * @param {string} [parameters.sortBy] - Sorting method
  538. * @param {string} [parameters.section] - The section name for the item Ex. "Products"
  539. * @param {object} [parameters.analyticsTags] - Pass additional analytics data
  540. * @param {object} [networkParameters] - Parameters relevant to the network request
  541. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  542. * @returns {(true|Error)}
  543. * @description User viewed a search product listing page
  544. * @example
  545. * constructorio.tracker.trackSearchResultsLoaded(
  546. * 'T-Shirt',
  547. * {
  548. * resultCount: 167,
  549. * items: [{itemId: 'KMH876'}, {itemId: 'KMH140'}, {itemId: 'KMH437'}],
  550. * sortOrder: 'ascending'
  551. * sortBy: 'price',
  552. * resultPage: 3,
  553. * resultCount: 20
  554. * },
  555. * );
  556. */
  557. trackSearchResultsLoaded(searchTerm, parameters, networkParameters = {}) {
  558. // Ensure term is provided (required)
  559. if (searchTerm && typeof searchTerm === 'string') {
  560. // Ensure parameters are provided (required)
  561. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  562. const baseUrl = `${this.options.serviceUrl}/v2/behavioral_action/search_result_load?`;
  563. const {
  564. num_results,
  565. numResults = num_results,
  566. result_count,
  567. customerIds,
  568. customer_ids = customerIds,
  569. itemIds,
  570. item_ids = itemIds,
  571. items = customer_ids || item_ids,
  572. result_page,
  573. resultPage = result_page,
  574. result_id,
  575. resultId = result_id,
  576. sort_order,
  577. sortOrder = sort_order,
  578. sort_by,
  579. sortBy = sort_by,
  580. selected_filters,
  581. selectedFilters = selected_filters,
  582. url = helpers.getWindowLocation()?.href || 'N/A',
  583. section,
  584. analyticsTags,
  585. resultCount = numResults ?? result_count ?? items?.length ?? 0,
  586. } = parameters;
  587. const queryParams = {};
  588. let transformedItems;
  589. if (items && Array.isArray(items) && items.length !== 0) {
  590. const trimmedItems = items.slice(0, 100);
  591. if (typeof items[0] === 'string' || typeof items[0] === 'number') {
  592. transformedItems = trimmedItems.map((itemId) => ({ item_id: String(itemId) }));
  593. } else {
  594. transformedItems = trimmedItems.map((item) => helpers.toSnakeCaseKeys(item, false));
  595. }
  596. }
  597. if (section) {
  598. queryParams.section = section;
  599. }
  600. const bodyParams = {
  601. search_term: searchTerm,
  602. result_count: resultCount,
  603. items: transformedItems,
  604. result_page: resultPage,
  605. result_id: resultId,
  606. sort_order: sortOrder,
  607. sort_by: sortBy,
  608. selected_filters: selectedFilters,
  609. url,
  610. section,
  611. analytics_tags: analyticsTags,
  612. };
  613. const requestURL = `${baseUrl}${applyParamsAsString(queryParams, this.options)}`;
  614. const requestMethod = 'POST';
  615. const requestBody = applyParams(bodyParams, {
  616. ...this.options,
  617. requestMethod,
  618. });
  619. this.requests.queue(
  620. requestURL,
  621. requestMethod,
  622. requestBody,
  623. networkParameters,
  624. );
  625. this.requests.send();
  626. return true;
  627. }
  628. this.requests.send();
  629. return new Error('parameters are required of type object');
  630. }
  631. this.requests.send();
  632. return new Error('term is a required parameter of type string');
  633. }
  634. /**
  635. * Send click through event to API
  636. * @private
  637. * @function trackSearchResultClickV2
  638. * @param {string} searchTerm - Search results query term
  639. * @param {object} parameters - Additional parameters to be sent with request
  640. * @param {string} parameters.itemName - Product item name (Either itemName or itemId is required)
  641. * @param {string} parameters.itemId - Product item unique identifier
  642. * @param {string} [parameters.variationId] - Product item variation unique identifier
  643. * @param {string} [parameters.resultId] - Search result identifier (returned in response from Constructor)
  644. * @param {number} [parameters.resultCount] - Number of results in total
  645. * @param {number} [parameters.resultPage] - Current page of results
  646. * @param {string} [parameters.resultPositionOnPage] - Position of selected items on page
  647. * @param {string} [parameters.numResultsPerPage] - Number of results per page
  648. * @param {object} [parameters.selectedFilters] - Key - Value map of selected filters
  649. * @param {string} [parameters.section] - The section name for the item Ex. "Products"
  650. * @param {object} [parameters.analyticsTags] - Pass additional analytics data
  651. * @param {object} [parameters.slCampaignId] - Pass campaign id of sponsored listing
  652. * @param {object} [parameters.slCampaignOwner] - Pass campaign owner of sponsored listing
  653. * @param {object} [networkParameters] - Parameters relevant to the network request
  654. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  655. * @returns {(true|Error)}
  656. * @description User clicked a result that appeared within a search product listing page
  657. * @example
  658. * constructorio.tracker.trackSearchResultClickV2(
  659. * 'T-Shirt',
  660. * {
  661. * itemName: 'Red T-Shirt',
  662. * itemId: 'KMH876',
  663. * resultId: '019927c2-f955-4020-8b8d-6b21b93cb5a2',
  664. * slCampaignId: 'Campaign 123',
  665. * slCampaignOwner: 'Store 123',
  666. * },
  667. * );
  668. */
  669. trackSearchResultClickV2(searchTerm, parameters, networkParameters = {}) {
  670. // Ensure term is provided (required)
  671. if (searchTerm && typeof searchTerm === 'string') {
  672. // Ensure parameters are provided (required)
  673. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  674. const baseUrl = `${this.options.serviceUrl}/v2/behavioral_action/search_result_click?`;
  675. const {
  676. num_results,
  677. customer_id,
  678. item_id,
  679. itemId = customer_id || item_id,
  680. name,
  681. item_name,
  682. itemName = name || item_name,
  683. variation_id,
  684. variationId = variation_id,
  685. result_id,
  686. resultId = result_id,
  687. result_count,
  688. resultCount = num_results || result_count,
  689. result_page,
  690. resultPage = result_page,
  691. result_position_on_page,
  692. resultPositionOnPage = result_position_on_page,
  693. num_results_per_page,
  694. numResultsPerPage = num_results_per_page,
  695. selected_filters,
  696. selectedFilters = selected_filters,
  697. section,
  698. analyticsTags,
  699. slCampaignId,
  700. slCampaignOwner,
  701. } = parameters;
  702. const bodyParams = {
  703. sl_campaign_id: slCampaignId,
  704. sl_campaign_owner: slCampaignOwner,
  705. item_name: itemName,
  706. item_id: itemId,
  707. variation_id: variationId,
  708. result_id: resultId,
  709. result_count: resultCount,
  710. result_page: resultPage,
  711. result_position_on_page: resultPositionOnPage,
  712. num_results_per_page: numResultsPerPage,
  713. selected_filters: selectedFilters,
  714. section,
  715. search_term: searchTerm,
  716. analytics_tags: analyticsTags,
  717. };
  718. const queryParams = {};
  719. if (section) {
  720. queryParams.section = section;
  721. }
  722. const requestURL = `${baseUrl}${applyParamsAsString(queryParams, this.options)}`;
  723. const requestMethod = 'POST';
  724. const requestBody = applyParams(bodyParams, {
  725. ...this.options,
  726. requestMethod,
  727. });
  728. this.requests.queue(
  729. requestURL,
  730. requestMethod,
  731. requestBody,
  732. networkParameters,
  733. );
  734. this.requests.send();
  735. return true;
  736. }
  737. this.requests.send();
  738. return new Error('parameters are required of type object');
  739. }
  740. this.requests.send();
  741. return new Error('term is a required parameter of type string');
  742. }
  743. /**
  744. * Send click through event to API
  745. *
  746. * @function trackSearchResultClick
  747. * @param {string} term - Search results query term
  748. * @param {object} parameters - Additional parameters to be sent with request
  749. * @param {string} parameters.itemName - Product item name
  750. * @param {string} parameters.itemId - Product item unique identifier
  751. * @param {string} [parameters.variationId] - Product item variation unique identifier
  752. * @param {string} [parameters.resultId] - Search result identifier (returned in response from Constructor)
  753. * @param {string} [parameters.itemIsConvertible] - Whether or not an item is available for a conversion
  754. * @param {string} [parameters.section] - The section name for the item Ex. "Products"
  755. * @param {object} [parameters.analyticsTags] - Pass additional analytics data
  756. * @param {object} [parameters.slCampaignId] - Pass campaign id of sponsored listing
  757. * @param {object} [parameters.slCampaignOwner] - Pass campaign owner of sponsored listing
  758. * @param {object} [networkParameters] - Parameters relevant to the network request
  759. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  760. * @returns {(true|Error)}
  761. * @description User clicked a result that appeared within a search product listing page
  762. * @example
  763. * constructorio.tracker.trackSearchResultClick(
  764. * 'T-Shirt',
  765. * {
  766. * itemName: 'Red T-Shirt',
  767. * itemId: 'KMH876',
  768. * resultId: '019927c2-f955-4020-8b8d-6b21b93cb5a2',
  769. * slCampaignId: 'Campaign 123',
  770. * slCampaignOwner: 'Store 123',
  771. * },
  772. * );
  773. */
  774. trackSearchResultClick(term, parameters, networkParameters = {}) {
  775. // Ensure term is provided (required)
  776. if (term && typeof term === 'string') {
  777. // Ensure parameters are provided (required)
  778. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  779. const url = `${this.options.serviceUrl}/autocomplete/${helpers.encodeURIComponentRFC3986(helpers.trimNonBreakingSpaces(term))}/click_through?`;
  780. const queryParams = {};
  781. const {
  782. item_name,
  783. name,
  784. itemName = item_name || name,
  785. item_id,
  786. itemId = item_id,
  787. customer_id,
  788. customerId = customer_id || itemId,
  789. variation_id,
  790. variationId = variation_id,
  791. result_id,
  792. resultId = result_id,
  793. item_is_convertible,
  794. itemIsConvertible = item_is_convertible,
  795. section,
  796. analyticsTags,
  797. slCampaignOwner,
  798. slCampaignId,
  799. } = parameters;
  800. // Ensure support for both item_name and name as parameters
  801. if (itemName) {
  802. queryParams.name = itemName;
  803. }
  804. // Ensure support for both item_id and customer_id as parameters
  805. if (customerId) {
  806. queryParams.customer_id = customerId;
  807. }
  808. if (variationId) {
  809. queryParams.variation_id = variationId;
  810. }
  811. if (resultId) {
  812. queryParams.result_id = resultId;
  813. }
  814. if (typeof itemIsConvertible === 'boolean') {
  815. queryParams.item_is_convertible = itemIsConvertible;
  816. }
  817. if (section) {
  818. queryParams.section = section;
  819. }
  820. if (analyticsTags) {
  821. queryParams.analytics_tags = analyticsTags;
  822. }
  823. if (slCampaignId) {
  824. queryParams.sl_campaign_id = slCampaignId;
  825. }
  826. if (slCampaignOwner) {
  827. queryParams.sl_campaign_owner = slCampaignOwner;
  828. }
  829. this.requests.queue(`${url}${applyParamsAsString(queryParams, this.options)}`, undefined, undefined, networkParameters);
  830. this.requests.send();
  831. return true;
  832. }
  833. this.requests.send();
  834. return new Error('parameters are required of type object');
  835. }
  836. this.requests.send();
  837. return new Error('term is a required parameter of type string');
  838. }
  839. /**
  840. * Send conversion event to API
  841. *
  842. * @function trackConversion
  843. * @param {string} [term] - Search results query term that led to conversion event
  844. * @param {object} parameters - Additional parameters to be sent with request
  845. * @param {string} parameters.itemId - Product item unique identifier
  846. * @param {number} [parameters.revenue] - Sale price if available, otherwise the regular (retail) price of item
  847. * @param {string} [parameters.itemName] - Product item name
  848. * @param {string} [parameters.variationId] - Product item variation unique identifier
  849. * @param {string} [parameters.type='add_to_cart'] - Conversion type
  850. * @param {boolean} [parameters.isCustomType] - Specify if type is custom conversion type
  851. * @param {string} [parameters.displayName] - Display name for the custom conversion type
  852. * @param {string} [parameters.section="Products"] - Index section
  853. * @param {object} [networkParameters] - Parameters relevant to the network request
  854. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  855. * @returns {(true|Error)}
  856. * @description User performed an action indicating interest in an item (add to cart, add to wishlist, etc.)
  857. * @see https://docs.constructor.com/docs/integrating-with-constructor-behavioral-tracking-data-driven-event-tracking
  858. * @example
  859. * constructorio.tracker.trackConversion(
  860. * 'T-Shirt',
  861. * {
  862. * itemId: 'KMH876',
  863. * revenue: 12.00,
  864. * itemName: 'Red T-Shirt',
  865. * variationId: 'KMH879-7632',
  866. * type: 'like',
  867. * resultId: '019927c2-f955-4020-8b8d-6b21b93cb5a2',
  868. * section: 'Products',
  869. * },
  870. * );
  871. */
  872. trackConversion(term, parameters, networkParameters = {}) {
  873. // Ensure parameters are provided (required)
  874. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  875. const searchTerm = term || 'TERM_UNKNOWN';
  876. const requestPath = `${this.options.serviceUrl}/v2/behavioral_action/conversion?`;
  877. const queryParams = {};
  878. const bodyParams = {};
  879. const {
  880. name,
  881. item_name,
  882. itemName = item_name || name,
  883. customer_id,
  884. customerId = customer_id,
  885. item_id,
  886. itemId = item_id || customerId,
  887. variation_id,
  888. variationId = variation_id,
  889. revenue,
  890. section = 'Products',
  891. display_name,
  892. displayName = display_name,
  893. type,
  894. is_custom_type,
  895. isCustomType = is_custom_type,
  896. } = parameters;
  897. // Ensure support for both item_id and customer_id as parameters
  898. if (itemId) {
  899. bodyParams.item_id = itemId;
  900. }
  901. // Ensure support for both item_name and name as parameters
  902. if (itemName) {
  903. bodyParams.item_name = itemName;
  904. }
  905. if (variationId) {
  906. bodyParams.variation_id = variationId;
  907. }
  908. if (revenue || revenue === 0) {
  909. bodyParams.revenue = revenue.toString();
  910. }
  911. if (section) {
  912. queryParams.section = section;
  913. bodyParams.section = section;
  914. }
  915. if (searchTerm) {
  916. bodyParams.search_term = searchTerm;
  917. }
  918. if (type) {
  919. bodyParams.type = type;
  920. }
  921. if (isCustomType) {
  922. bodyParams.is_custom_type = isCustomType;
  923. }
  924. if (displayName) {
  925. bodyParams.display_name = displayName;
  926. }
  927. const requestURL = `${requestPath}${applyParamsAsString(queryParams, this.options)}`;
  928. const requestMethod = 'POST';
  929. const requestBody = applyParams(bodyParams, { ...this.options, requestMethod });
  930. this.requests.queue(
  931. requestURL,
  932. requestMethod,
  933. requestBody,
  934. networkParameters,
  935. );
  936. this.requests.send();
  937. return true;
  938. }
  939. this.requests.send();
  940. return new Error('parameters are required of type object');
  941. }
  942. /**
  943. * Send purchase event to API
  944. *
  945. * @function trackPurchase
  946. * @param {object} parameters - Additional parameters to be sent with request
  947. * @param {Array.<{itemId: string | undefined,
  948. * variationId: string | undefined,
  949. * itemName: string | undefined,
  950. * count: number | undefined,
  951. * price: number | undefined}>} parameters.items - List of product item objects
  952. * @param {number} parameters.revenue - The subtotal (excluding taxes, shipping, etc.) of the entire order
  953. * @param {string} [parameters.orderId] - Unique order identifier
  954. * @param {string} [parameters.section="Products"] - Index section
  955. * @param {object} [parameters.analyticsTags] - Pass additional analytics data
  956. * @param {object} [networkParameters] - Parameters relevant to the network request
  957. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  958. * @returns {(true|Error)}
  959. * @description User completed an order (usually fired on order confirmation page)
  960. * @example
  961. * constructorio.tracker.trackPurchase(
  962. * {
  963. * items: [{ itemId: 'KMH876', price: 1, count: 1}, { itemId: 'KMH140', price: 1, count: 1s }],
  964. * revenue: 12.00,
  965. * orderId: 'OUNXBG2HMA',
  966. * section: 'Products',
  967. * },
  968. * );
  969. */
  970. trackPurchase(parameters, networkParameters = {}) {
  971. // Ensure parameters are provided (required)
  972. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  973. const requestPath = `${this.options.serviceUrl}/v2/behavioral_action/purchase?`;
  974. const queryParams = {};
  975. const bodyParams = {};
  976. const {
  977. items,
  978. revenue,
  979. order_id,
  980. orderId = order_id,
  981. section,
  982. analyticsTags,
  983. } = parameters;
  984. const { apiKey } = this.options;
  985. if (orderId) {
  986. // Don't send another purchase event if we have already tracked the order for the current key
  987. if (helpers.hasOrderIdRecord({ orderId, apiKey })) {
  988. return false;
  989. }
  990. helpers.addOrderIdRecord({ orderId, apiKey });
  991. // Add order_id to the tracking params
  992. bodyParams.order_id = orderId;
  993. }
  994. if (items && Array.isArray(items)) {
  995. bodyParams.items = items.slice(0, 100).map((item) => helpers.toSnakeCaseKeys(item, false));
  996. }
  997. if (revenue || revenue === 0) {
  998. bodyParams.revenue = revenue;
  999. }
  1000. if (section) {
  1001. queryParams.section = section;
  1002. } else {
  1003. queryParams.section = 'Products';
  1004. }
  1005. if (analyticsTags) {
  1006. bodyParams.analytics_tags = analyticsTags;
  1007. }
  1008. const requestURL = `${requestPath}${applyParamsAsString(queryParams, this.options)}`;
  1009. const requestMethod = 'POST';
  1010. const requestBody = applyParams(bodyParams, { ...this.options, requestMethod });
  1011. this.requests.queue(
  1012. requestURL,
  1013. requestMethod,
  1014. requestBody,
  1015. networkParameters,
  1016. );
  1017. this.requests.send();
  1018. return true;
  1019. }
  1020. this.requests.send();
  1021. return new Error('parameters are required of type object');
  1022. }
  1023. /**
  1024. * Send recommendation view event to API
  1025. *
  1026. * @function trackRecommendationView
  1027. * @param {object} parameters - Additional parameters to be sent with request
  1028. * @param {string} parameters.url - Current page URL
  1029. * @param {string} parameters.podId - Pod identifier
  1030. * @param {number} parameters.numResultsViewed - Number of results viewed
  1031. * @param {object[]} [parameters.items] - List of Product Item Objects
  1032. * @param {number} [parameters.resultCount] - Total number of results
  1033. * @param {number} [parameters.resultPage] - Page number of results
  1034. * @param {string} [parameters.resultId] - Recommendation result identifier (returned in response from Constructor)
  1035. * @param {string} [parameters.section="Products"] - Results section
  1036. * @param {object} [parameters.analyticsTags] - Pass additional analytics data
  1037. * @param {object} [networkParameters] - Parameters relevant to the network request
  1038. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  1039. * @returns {(true|Error)}
  1040. * @description User viewed a set of recommendations
  1041. * @example
  1042. * constructorio.tracker.trackRecommendationView(
  1043. * {
  1044. * items: [{ itemId: 'KMH876' }, { itemId: 'KMH140' }],
  1045. * resultCount: 22,
  1046. * resultPage: 2,
  1047. * resultId: '019927c2-f955-4020-8b8d-6b21b93cb5a2',
  1048. * url: 'https://demo.constructor.io/sandbox/farmstand',
  1049. * podId: '019927c2-f955-4020',
  1050. * numResultsViewed: 3,
  1051. * },
  1052. * );
  1053. */
  1054. trackRecommendationView(parameters, networkParameters = {}) {
  1055. // Ensure parameters are provided (required)
  1056. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  1057. const requestPath = `${this.options.serviceUrl}/v2/behavioral_action/recommendation_result_view?`;
  1058. const bodyParams = {};
  1059. const {
  1060. result_count,
  1061. result_page,
  1062. resultPage = result_page,
  1063. result_id,
  1064. resultId = result_id,
  1065. section,
  1066. url,
  1067. pod_id,
  1068. podId = pod_id,
  1069. num_results_viewed,
  1070. numResultsViewed = num_results_viewed,
  1071. items,
  1072. analyticsTags,
  1073. resultCount = result_count || items?.length || 0,
  1074. } = parameters;
  1075. if (!helpers.isNil(resultCount)) {
  1076. bodyParams.result_count = resultCount;
  1077. }
  1078. if (!helpers.isNil(resultPage)) {
  1079. bodyParams.result_page = resultPage;
  1080. }
  1081. if (resultId) {
  1082. bodyParams.result_id = resultId;
  1083. }
  1084. if (section) {
  1085. bodyParams.section = section;
  1086. } else {
  1087. bodyParams.section = 'Products';
  1088. }
  1089. if (url) {
  1090. bodyParams.url = url;
  1091. }
  1092. if (podId) {
  1093. bodyParams.pod_id = podId;
  1094. }
  1095. if (!helpers.isNil(numResultsViewed)) {
  1096. bodyParams.num_results_viewed = numResultsViewed;
  1097. }
  1098. if (items && Array.isArray(items)) {
  1099. bodyParams.items = items.slice(0, 100).map((item) => helpers.toSnakeCaseKeys(item, false));
  1100. }
  1101. if (analyticsTags) {
  1102. bodyParams.analytics_tags = analyticsTags;
  1103. }
  1104. const requestURL = `${requestPath}${applyParamsAsString({}, this.options)}`;
  1105. const requestMethod = 'POST';
  1106. const requestBody = applyParams(bodyParams, { ...this.options, requestMethod });
  1107. this.requests.queue(
  1108. requestURL,
  1109. requestMethod,
  1110. requestBody,
  1111. networkParameters,
  1112. );
  1113. this.requests.send();
  1114. return true;
  1115. }
  1116. this.requests.send();
  1117. return new Error('parameters are required of type object');
  1118. }
  1119. /**
  1120. * Send recommendation click event to API
  1121. *
  1122. * @function trackRecommendationClick
  1123. * @param {object} parameters - Additional parameters to be sent with request
  1124. * @param {string} parameters.podId - Pod identifier
  1125. * @param {string} parameters.strategyId - Strategy identifier
  1126. * @param {string} parameters.itemId - Product item unique identifier
  1127. * @param {string} parameters.itemName - Product item name
  1128. * @param {string} [parameters.variationId] - Product item variation unique identifier
  1129. * @param {string} [parameters.section="Products"] - Index section
  1130. * @param {string} [parameters.resultId] - Recommendation result identifier (returned in response from Constructor)
  1131. * @param {number} [parameters.resultCount] - Total number of results
  1132. * @param {number} [parameters.resultPage] - Page number of results
  1133. * @param {number} [parameters.resultPositionOnPage] - Position of result on page
  1134. * @param {number} [parameters.numResultsPerPage] - Number of results on page
  1135. * @param {object} [parameters.analyticsTags] - Pass additional analytics data
  1136. * @param {object} [networkParameters] - Parameters relevant to the network request
  1137. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  1138. * @returns {(true|Error)}
  1139. * @description User clicked an item that appeared within a list of recommended results
  1140. * @example
  1141. * constructorio.tracker.trackRecommendationClick(
  1142. * {
  1143. * variationId: 'KMH879-7632',
  1144. * resultId: '019927c2-f955-4020-8b8d-6b21b93cb5a2',
  1145. * resultCount: 22,
  1146. * resultPage: 2,
  1147. * resultPositionOnPage: 2,
  1148. * numResultsPerPage: 12,
  1149. * podId: '019927c2-f955-4020',
  1150. * strategyId: 'complimentary',
  1151. * itemId: 'KMH876',
  1152. * itemName: 'Socks',
  1153. * },
  1154. * );
  1155. */
  1156. trackRecommendationClick(parameters, networkParameters = {}) {
  1157. // Ensure parameters are provided (required)
  1158. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  1159. const requestPath = `${this.options.serviceUrl}/v2/behavioral_action/recommendation_result_click?`;
  1160. const bodyParams = {};
  1161. const {
  1162. variation_id,
  1163. variationId = variation_id,
  1164. section = 'Products',
  1165. result_id,
  1166. resultId = result_id,
  1167. result_count,
  1168. resultCount = result_count,
  1169. result_page,
  1170. resultPage = result_page,
  1171. result_position_on_page,
  1172. resultPositionOnPage = result_position_on_page,
  1173. num_results_per_page,
  1174. numResultsPerPage = num_results_per_page,
  1175. pod_id,
  1176. podId = pod_id,
  1177. strategy_id,
  1178. strategyId = strategy_id,
  1179. item_id,
  1180. itemId = item_id,
  1181. item_name,
  1182. itemName = item_name,
  1183. analyticsTags,
  1184. } = parameters;
  1185. if (variationId) {
  1186. bodyParams.variation_id = variationId;
  1187. }
  1188. if (section) {
  1189. bodyParams.section = section;
  1190. }
  1191. if (resultId) {
  1192. bodyParams.result_id = resultId;
  1193. }
  1194. if (!helpers.isNil(resultCount)) {
  1195. bodyParams.result_count = resultCount;
  1196. }
  1197. if (!helpers.isNil(resultPage)) {
  1198. bodyParams.result_page = resultPage;
  1199. }
  1200. if (!helpers.isNil(resultPositionOnPage)) {
  1201. bodyParams.result_position_on_page = resultPositionOnPage;
  1202. }
  1203. if (!helpers.isNil(numResultsPerPage)) {
  1204. bodyParams.num_results_per_page = numResultsPerPage;
  1205. }
  1206. if (podId) {
  1207. bodyParams.pod_id = podId;
  1208. }
  1209. if (strategyId) {
  1210. bodyParams.strategy_id = strategyId;
  1211. }
  1212. if (itemId) {
  1213. bodyParams.item_id = itemId;
  1214. }
  1215. if (itemName) {
  1216. bodyParams.item_name = itemName;
  1217. }
  1218. if (analyticsTags) {
  1219. bodyParams.analytics_tags = analyticsTags;
  1220. }
  1221. const requestURL = `${requestPath}${applyParamsAsString({}, this.options)}`;
  1222. const requestMethod = 'POST';
  1223. const requestBody = applyParams(bodyParams, { ...this.options, requestMethod });
  1224. this.requests.queue(
  1225. requestURL,
  1226. requestMethod,
  1227. requestBody,
  1228. networkParameters,
  1229. );
  1230. this.requests.send();
  1231. return true;
  1232. }
  1233. this.requests.send();
  1234. return new Error('parameters are required of type object');
  1235. }
  1236. /**
  1237. * Send browse results loaded event to API
  1238. *
  1239. * @function trackBrowseResultsLoaded
  1240. * @param {object} parameters - Additional parameters to be sent with request
  1241. * @param {string} parameters.url - Current page URL
  1242. * @param {string} parameters.filterName - Filter name
  1243. * @param {string} parameters.filterValue - Filter value
  1244. * @param {object[]} parameters.items - List of product item objects
  1245. * @param {string} [parameters.section="Products"] - Index section
  1246. * @param {number} [parameters.resultCount] - Total number of results
  1247. * @param {number} [parameters.resultPage] - Page number of results
  1248. * @param {string} [parameters.resultId] - Browse result identifier (returned in response from Constructor)
  1249. * @param {object} [parameters.selectedFilters] - Selected filters
  1250. * @param {string} [parameters.sortOrder] - Sort order ('ascending' or 'descending')
  1251. * @param {string} [parameters.sortBy] - Sorting method
  1252. * @param {object} [parameters.analyticsTags] - Pass additional analytics data
  1253. * @param {object} [networkParameters] - Parameters relevant to the network request
  1254. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  1255. * @returns {(true|Error)}
  1256. * @description User viewed a browse product listing page
  1257. * @example
  1258. * constructorio.tracker.trackBrowseResultsLoaded(
  1259. * {
  1260. * resultCount: 22,
  1261. * resultPage: 2,
  1262. * resultId: '019927c2-f955-4020-8b8d-6b21b93cb5a2',
  1263. * selectedFilters: { brand: ['foo'], color: ['black'] },
  1264. * sortOrder: 'ascending',
  1265. * sortBy: 'price',
  1266. * items: [{ itemId: 'KMH876' }, { itemId: 'KMH140' }],
  1267. * url: 'https://demo.constructor.io/sandbox/farmstand',
  1268. * filterName: 'brand',
  1269. * filterValue: 'XYZ',
  1270. * },
  1271. * );
  1272. */
  1273. trackBrowseResultsLoaded(parameters, networkParameters = {}) {
  1274. // Ensure parameters are provided (required)
  1275. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  1276. const requestPath = `${this.options.serviceUrl}/v2/behavioral_action/browse_result_load?`;
  1277. const bodyParams = {};
  1278. const {
  1279. section = 'Products',
  1280. result_count,
  1281. result_page,
  1282. resultPage = result_page,
  1283. result_id,
  1284. resultId = result_id,
  1285. selected_filters,
  1286. selectedFilters = selected_filters,
  1287. url,
  1288. sort_order,
  1289. sortOrder = sort_order,
  1290. sort_by,
  1291. sortBy = sort_by,
  1292. filter_name,
  1293. filterName = filter_name,
  1294. filter_value,
  1295. filterValue = filter_value,
  1296. items,
  1297. analyticsTags,
  1298. resultCount = result_count ?? items?.length ?? 0,
  1299. } = parameters;
  1300. if (section) {
  1301. bodyParams.section = section;
  1302. }
  1303. if (!helpers.isNil(resultCount)) {
  1304. bodyParams.result_count = resultCount;
  1305. }
  1306. if (!helpers.isNil(resultPage)) {
  1307. bodyParams.result_page = resultPage;
  1308. }
  1309. if (resultId) {
  1310. bodyParams.result_id = resultId;
  1311. }
  1312. if (selectedFilters) {
  1313. bodyParams.selected_filters = selectedFilters;
  1314. }
  1315. if (url) {
  1316. bodyParams.url = url;
  1317. }
  1318. if (sortOrder) {
  1319. bodyParams.sort_order = sortOrder;
  1320. }
  1321. if (sortBy) {
  1322. bodyParams.sort_by = sortBy;
  1323. }
  1324. if (filterName) {
  1325. bodyParams.filter_name = filterName;
  1326. }
  1327. if (filterValue) {
  1328. bodyParams.filter_value = filterValue;
  1329. }
  1330. if (items && Array.isArray(items)) {
  1331. bodyParams.items = items.slice(0, 100).map((item) => helpers.toSnakeCaseKeys(item, false));
  1332. }
  1333. if (analyticsTags) {
  1334. bodyParams.analytics_tags = analyticsTags;
  1335. }
  1336. const requestURL = `${requestPath}${applyParamsAsString({}, this.options)}`;
  1337. const requestMethod = 'POST';
  1338. const requestBody = applyParams(bodyParams, { ...this.options, requestMethod });
  1339. this.requests.queue(
  1340. requestURL,
  1341. requestMethod,
  1342. requestBody,
  1343. networkParameters,
  1344. );
  1345. this.requests.send();
  1346. return true;
  1347. }
  1348. this.requests.send();
  1349. return new Error('parameters are required of type object');
  1350. }
  1351. /**
  1352. * Send browse result click event to API
  1353. *
  1354. * @function trackBrowseResultClick
  1355. * @param {object} parameters - Additional parameters to be sent with request
  1356. * @param {string} parameters.filterName - Filter name
  1357. * @param {string} parameters.filterValue - Filter value
  1358. * @param {string} parameters.itemId - Product item unique identifier
  1359. * @param {string} parameters.itemName - Product item name
  1360. * @param {string} [parameters.section="Products"] - Index section
  1361. * @param {string} [parameters.variationId] - Product item variation unique identifier
  1362. * @param {string} [parameters.resultId] - Browse result identifier (returned in response from Constructor)
  1363. * @param {number} [parameters.resultCount] - Total number of results
  1364. * @param {number} [parameters.resultPage] - Page number of results
  1365. * @param {number} [parameters.resultPositionOnPage] - Position of clicked item
  1366. * @param {number} [parameters.numResultsPerPage] - Number of results shown
  1367. * @param {object} [parameters.selectedFilters] - Selected filters
  1368. * @param {object} [parameters.analyticsTags] - Pass additional analytics data
  1369. * @param {object} [parameters.slCampaignId] - Pass campaign id of sponsored listing
  1370. * @param {object} [parameters.slCampaignOwner] - Pass campaign owner of sponsored listing
  1371. * @param {object} [networkParameters] - Parameters relevant to the network request
  1372. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  1373. * @returns {(true|Error)}
  1374. * @description User clicked a result that appeared within a browse product listing page
  1375. * @example
  1376. * constructorio.tracker.trackBrowseResultClick(
  1377. * {
  1378. * variationId: 'KMH879-7632',
  1379. * resultId: '019927c2-f955-4020-8b8d-6b21b93cb5a2',
  1380. * resultCount: 22,
  1381. * resultPage: 2,
  1382. * resultPositionOnPage: 2,
  1383. * numResultsPerPage: 12,
  1384. * selectedFilters: { brand: ['foo'], color: ['black'] },
  1385. * filterName: 'brand',
  1386. * filterValue: 'XYZ',
  1387. * itemId: 'KMH876',
  1388. * slCampaignId: 'Campaign 123',
  1389. * slCampaignOwner: 'Store 123',
  1390. * },
  1391. * );
  1392. */
  1393. trackBrowseResultClick(parameters, networkParameters = {}) {
  1394. // Ensure parameters are provided (required)
  1395. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  1396. const requestPath = `${this.options.serviceUrl}/v2/behavioral_action/browse_result_click?`;
  1397. const bodyParams = {};
  1398. const {
  1399. section = 'Products',
  1400. variation_id,
  1401. variationId = variation_id,
  1402. result_id,
  1403. resultId = result_id,
  1404. result_count,
  1405. resultCount = result_count,
  1406. result_page,
  1407. resultPage = result_page,
  1408. result_position_on_page,
  1409. resultPositionOnPage = result_position_on_page,
  1410. num_results_per_page,
  1411. numResultsPerPage = num_results_per_page,
  1412. selected_filters,
  1413. selectedFilters = selected_filters,
  1414. filter_name,
  1415. filterName = filter_name,
  1416. filter_value,
  1417. filterValue = filter_value,
  1418. customer_id,
  1419. customerId = customer_id,
  1420. item_id,
  1421. itemId = customerId || item_id,
  1422. item_name,
  1423. name,
  1424. itemName = item_name || name,
  1425. analyticsTags,
  1426. slCampaignId,
  1427. slCampaignOwner,
  1428. } = parameters;
  1429. if (section) {
  1430. bodyParams.section = section;
  1431. }
  1432. if (variationId) {
  1433. bodyParams.variation_id = variationId;
  1434. }
  1435. if (resultId) {
  1436. bodyParams.result_id = resultId;
  1437. }
  1438. if (!helpers.isNil(resultCount)) {
  1439. bodyParams.result_count = resultCount;
  1440. }
  1441. if (!helpers.isNil(resultPage)) {
  1442. bodyParams.result_page = resultPage;
  1443. }
  1444. if (!helpers.isNil(resultPositionOnPage)) {
  1445. bodyParams.result_position_on_page = resultPositionOnPage;
  1446. }
  1447. if (!helpers.isNil(numResultsPerPage)) {
  1448. bodyParams.num_results_per_page = numResultsPerPage;
  1449. }
  1450. if (selectedFilters) {
  1451. bodyParams.selected_filters = selectedFilters;
  1452. }
  1453. if (filterName) {
  1454. bodyParams.filter_name = filterName;
  1455. }
  1456. if (filterValue) {
  1457. bodyParams.filter_value = filterValue;
  1458. }
  1459. if (itemId) {
  1460. bodyParams.item_id = itemId;
  1461. }
  1462. // Ensure support for both item_name and name as parameters
  1463. if (itemName) {
  1464. bodyParams.item_name = itemName;
  1465. }
  1466. if (analyticsTags) {
  1467. bodyParams.analytics_tags = analyticsTags;
  1468. }
  1469. if (slCampaignId) {
  1470. bodyParams.sl_campaign_id = slCampaignId;
  1471. }
  1472. if (slCampaignOwner) {
  1473. bodyParams.sl_campaign_owner = slCampaignOwner;
  1474. }
  1475. const requestURL = `${requestPath}${applyParamsAsString({}, this.options)}`;
  1476. const requestMethod = 'POST';
  1477. const requestBody = applyParams(bodyParams, { ...this.options, requestMethod });
  1478. this.requests.queue(
  1479. requestURL,
  1480. requestMethod,
  1481. requestBody,
  1482. networkParameters,
  1483. );
  1484. this.requests.send();
  1485. return true;
  1486. }
  1487. this.requests.send();
  1488. return new Error('parameters are required of type object');
  1489. }
  1490. /**
  1491. * Send browse redirect event to API
  1492. * @private
  1493. * @function trackBrowseRedirect
  1494. * @param {object} parameters - Additional parameters to be sent with request
  1495. * @param {string} parameters.searchTerm - The search query that caused redirect
  1496. * @param {string} parameters.filterName - Filter name
  1497. * @param {string} parameters.filterValue - Filter value
  1498. * @param {string} [parameters.userInput] - The text that a user had typed at the moment when submitting search request
  1499. * @param {string} [parameters.redirectToUrl] - URL of the page to which user is redirected
  1500. * @param {string} [parameters.section="Products"] - Index section
  1501. * @param {object} [parameters.selectedFilters] - Selected filters
  1502. * @param {string} [parameters.sortOrder] - Sort order ('ascending' or 'descending')
  1503. * @param {string} [parameters.sortBy] - Sorting method
  1504. * @param {object} [parameters.analyticsTags] - Pass additional analytics data
  1505. * @param {object} [networkParameters] - Parameters relevant to the network request
  1506. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  1507. * @returns {(true|Error)}
  1508. * @description User got redirected to a browse product listing page
  1509. * @example
  1510. * constructorio.tracker.trackBrowseRedirect(
  1511. * {
  1512. * searchTerm: "books",
  1513. * filterName: 'brand',
  1514. * filterValue: 'XYZ',
  1515. * redirectToUrl: 'https://demo.constructor.io/books',
  1516. * selectedFilters: { brand: ['foo'], color: ['black'] },
  1517. * sortOrder: 'ascending',
  1518. * sortBy: 'price',
  1519. * },
  1520. * );
  1521. */
  1522. trackBrowseRedirect(parameters, networkParameters = {}) {
  1523. // Ensure parameters are provided (required)
  1524. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  1525. const requestPath = `${this.options.serviceUrl}/v2/behavioral_action/browse_redirect?`;
  1526. const bodyParams = {};
  1527. const {
  1528. searchTerm,
  1529. userInput,
  1530. section = 'Products',
  1531. selectedFilters,
  1532. redirectToUrl,
  1533. sortOrder,
  1534. sortBy,
  1535. filterName,
  1536. filterValue,
  1537. analyticsTags,
  1538. } = parameters;
  1539. if (searchTerm) {
  1540. bodyParams.search_term = searchTerm;
  1541. }
  1542. if (userInput) {
  1543. bodyParams.user_input = userInput;
  1544. }
  1545. if (redirectToUrl) {
  1546. bodyParams.redirect_to_url = redirectToUrl;
  1547. }
  1548. if (section) {
  1549. bodyParams.section = section;
  1550. }
  1551. if (selectedFilters) {
  1552. bodyParams.selected_filters = selectedFilters;
  1553. }
  1554. if (sortOrder) {
  1555. bodyParams.sort_order = sortOrder;
  1556. }
  1557. if (sortBy) {
  1558. bodyParams.sort_by = sortBy;
  1559. }
  1560. if (filterName) {
  1561. bodyParams.filter_name = filterName;
  1562. }
  1563. if (filterValue) {
  1564. bodyParams.filter_value = filterValue;
  1565. }
  1566. if (analyticsTags) {
  1567. bodyParams.analytics_tags = analyticsTags;
  1568. }
  1569. const requestURL = `${requestPath}${applyParamsAsString({}, this.options)}`;
  1570. const requestMethod = 'POST';
  1571. const requestBody = applyParams(bodyParams, { ...this.options, requestMethod });
  1572. this.requests.queue(
  1573. requestURL,
  1574. requestMethod,
  1575. requestBody,
  1576. networkParameters,
  1577. );
  1578. this.requests.send();
  1579. return true;
  1580. }
  1581. this.requests.send();
  1582. return new Error('parameters are required of type object');
  1583. }
  1584. /**
  1585. * Send generic result click event to API
  1586. *
  1587. * @function trackGenericResultClick
  1588. * @param {object} parameters - Additional parameters to be sent with request
  1589. * @param {string} parameters.itemId - Product item unique identifier
  1590. * @param {string} [parameters.itemName] - Product item name
  1591. * @param {string} [parameters.variationId] - Product item variation unique identifier
  1592. * @param {string} [parameters.section="Products"] - Index section
  1593. * @param {object} [parameters.analyticsTags] - Pass additional analytics data
  1594. * @param {object} [networkParameters] - Parameters relevant to the network request
  1595. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  1596. * @returns {(true|Error)}
  1597. * @description User clicked a result that appeared outside of the scope of search / browse / recommendations
  1598. * @example
  1599. * constructorio.tracker.trackGenericResultClick(
  1600. * {
  1601. * itemId: 'KMH876',
  1602. * itemName: 'Red T-Shirt',
  1603. * variationId: 'KMH879-7632',
  1604. * },
  1605. * );
  1606. */
  1607. trackGenericResultClick(parameters, networkParameters = {}) {
  1608. // Ensure required parameters are provided
  1609. if (typeof parameters === 'object') {
  1610. const requestPath = `${this.options.serviceUrl}/v2/behavioral_action/result_click?`;
  1611. const bodyParams = {};
  1612. const {
  1613. item_id,
  1614. itemId = item_id,
  1615. item_name,
  1616. itemName = item_name,
  1617. variation_id,
  1618. variationId = variation_id,
  1619. section = 'Products',
  1620. analyticsTags,
  1621. } = parameters;
  1622. if (itemId) {
  1623. bodyParams.section = section;
  1624. bodyParams.item_id = itemId;
  1625. if (itemName) {
  1626. bodyParams.item_name = itemName;
  1627. }
  1628. if (variationId) {
  1629. bodyParams.variation_id = variationId;
  1630. }
  1631. if (analyticsTags) {
  1632. bodyParams.analytics_tags = analyticsTags;
  1633. }
  1634. const requestURL = `${requestPath}${applyParamsAsString({}, this.options)}`;
  1635. const requestMethod = 'POST';
  1636. const requestBody = applyParams(bodyParams, { ...this.options, requestMethod });
  1637. this.requests.queue(
  1638. requestURL,
  1639. requestMethod,
  1640. requestBody,
  1641. networkParameters,
  1642. );
  1643. this.requests.send();
  1644. return true;
  1645. }
  1646. }
  1647. this.requests.send();
  1648. return new Error('A parameters object with an "itemId" property is required.');
  1649. }
  1650. /**
  1651. * Send quiz results loaded event to API
  1652. *
  1653. * @function trackQuizResultsLoaded
  1654. * @param {object} parameters - Additional parameters to be sent with request
  1655. * @param {string} parameters.quizId - Quiz identifier
  1656. * @param {string} parameters.quizVersionId - Quiz version identifier
  1657. * @param {string} parameters.quizSessionId - Quiz session identifier associated with this conversion event
  1658. * @param {string} parameters.url - Current page url
  1659. * @param {object[]} parameters.items - List of product item objects
  1660. * @param {string} [parameters.section='Products'] - Index section
  1661. * @param {number} [parameters.resultCount] - Total number of results
  1662. * @param {number} [parameters.resultPage] - The page of the results
  1663. * @param {string} [parameters.resultId] - Quiz result identifier (returned in response from Constructor)
  1664. * @param {object} [networkParameters] - Parameters relevant to the network request
  1665. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  1666. * @returns {(true|Error)}
  1667. * @description User viewed a quiz results page
  1668. * @example
  1669. * constructorio.tracker.trackQuizResultsLoaded(
  1670. * {
  1671. * quizId: 'coffee-quiz',
  1672. * quizVersionId: '1231244',
  1673. * quizSessionId: '3123',
  1674. * url: 'www.example.com',
  1675. * resultCount: 167,
  1676. * items: [{ itemId: 'KMH876', itemName: 'coffee' }, { itemId: 'KMH140', variationId: '123' }],
  1677. * },
  1678. * );
  1679. */
  1680. // eslint-disable-next-line complexity
  1681. trackQuizResultsLoaded(parameters, networkParameters = {}) {
  1682. // Ensure parameters are provided (required)
  1683. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  1684. const requestPath = `${this.options.serviceUrl}/v2/behavioral_action/quiz_result_load?`;
  1685. const {
  1686. quiz_id,
  1687. quizId = quiz_id,
  1688. quiz_version_id,
  1689. quizVersionId = quiz_version_id,
  1690. quiz_session_id,
  1691. quizSessionId = quiz_session_id,
  1692. url,
  1693. section = 'Products',
  1694. result_count,
  1695. resultCount = result_count,
  1696. result_id,
  1697. resultId = result_id,
  1698. result_page,
  1699. resultPage = result_page,
  1700. items,
  1701. } = parameters;
  1702. const queryParams = {};
  1703. const bodyParams = {};
  1704. if (items && Array.isArray(items)) {
  1705. bodyParams.items = items.slice(0, 100).map((item) => helpers.toSnakeCaseKeys(item, false));
  1706. }
  1707. if (typeof quizId !== 'string') {
  1708. return new Error('"quizId" is a required parameter of type string');
  1709. }
  1710. if (typeof quizVersionId !== 'string') {
  1711. return new Error('"quizVersionId" is a required parameter of type string');
  1712. }
  1713. if (typeof quizSessionId !== 'string') {
  1714. return new Error('"quizSessionId" is a required parameter of type string');
  1715. }
  1716. if (typeof url !== 'string') {
  1717. return new Error('"url" is a required parameter of type string');
  1718. }
  1719. bodyParams.quiz_id = quizId;
  1720. bodyParams.quiz_version_id = quizVersionId;
  1721. bodyParams.quiz_session_id = quizSessionId;
  1722. bodyParams.url = url;
  1723. if (!helpers.isNil(section)) {
  1724. if (typeof section !== 'string') {
  1725. return new Error('"section" must be a string');
  1726. }
  1727. queryParams.section = section;
  1728. bodyParams.section = section;
  1729. }
  1730. if (!helpers.isNil(resultCount)) {
  1731. if (typeof resultCount !== 'number') {
  1732. return new Error('"resultCount" must be a number');
  1733. }
  1734. bodyParams.result_count = resultCount;
  1735. }
  1736. if (!helpers.isNil(resultId)) {
  1737. if (typeof resultId !== 'string') {
  1738. return new Error('"resultId" must be a string');
  1739. }
  1740. bodyParams.result_id = resultId;
  1741. }
  1742. if (!helpers.isNil(resultPage)) {
  1743. if (typeof resultPage !== 'number') {
  1744. return new Error('"resultPage" must be a number');
  1745. }
  1746. bodyParams.result_page = resultPage;
  1747. }
  1748. const requestURL = `${requestPath}${applyParamsAsString(queryParams, this.options)}`;
  1749. const requestMethod = 'POST';
  1750. const requestBody = applyParams(bodyParams, { ...this.options, requestMethod });
  1751. this.requests.queue(
  1752. requestURL,
  1753. requestMethod,
  1754. requestBody,
  1755. networkParameters,
  1756. );
  1757. this.requests.send();
  1758. return true;
  1759. }
  1760. this.requests.send();
  1761. return new Error('parameters are required of type object');
  1762. }
  1763. /**
  1764. * Send quiz result click event to API
  1765. *
  1766. * @function trackQuizResultClick
  1767. * @param {object} parameters - Additional parameters to be sent with request
  1768. * @param {string} parameters.quizId - Quiz identifier
  1769. * @param {string} parameters.quizVersionId - Quiz version identifier
  1770. * @param {string} parameters.quizSessionId - Quiz session identifier associated with this conversion event
  1771. * @param {string} [parameters.itemId] - Product item unique identifier (Either itemId or itemName is required)
  1772. * @param {string} [parameters.itemName] - Product item name
  1773. * @param {string} [parameters.variationId] - Product variation unique identifier
  1774. * @param {string} [parameters.section='Products'] - Index section
  1775. * @param {number} [parameters.resultCount] - Total number of results
  1776. * @param {number} [parameters.resultPage] - The page of the results
  1777. * @param {string} [parameters.resultId] - Quiz result identifier (returned in response from Constructor)
  1778. * @param {number} [parameters.resultPositionOnPage] - Position of clicked item
  1779. * @param {number} [parameters.numResultsPerPage] - Number of results shown
  1780. * @param {object} [networkParameters] - Parameters relevant to the network request
  1781. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  1782. * @returns {(true|Error)}
  1783. * @description User viewed a quiz results page
  1784. * @example
  1785. * constructorio.tracker.trackQuizResultClick(
  1786. * {
  1787. * quizId: 'coffee-quiz',
  1788. * quizVersionId: '1231244',
  1789. * quizSessionId: '123',
  1790. * itemId: '123',
  1791. * itemName: 'espresso'
  1792. * variationId: '733431',
  1793. * },
  1794. * );
  1795. */
  1796. // eslint-disable-next-line complexity
  1797. trackQuizResultClick(parameters, networkParameters = {}) {
  1798. // Ensure parameters are provided (required)
  1799. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  1800. const requestPath = `${this.options.serviceUrl}/v2/behavioral_action/quiz_result_click?`;
  1801. const {
  1802. quiz_id,
  1803. quizId = quiz_id,
  1804. quiz_version_id,
  1805. quizVersionId = quiz_version_id,
  1806. quiz_session_id,
  1807. quizSessionId = quiz_session_id,
  1808. item_id,
  1809. itemId = item_id,
  1810. item_name,
  1811. itemName = item_name,
  1812. result_count,
  1813. resultCount = result_count,
  1814. result_id,
  1815. resultId = result_id,
  1816. result_page,
  1817. resultPage = result_page,
  1818. num_results_per_page,
  1819. numResultsPerPage = num_results_per_page,
  1820. result_position_on_page,
  1821. resultPositionOnPage = result_position_on_page,
  1822. section = 'Products',
  1823. variationId,
  1824. } = parameters;
  1825. const queryParams = {};
  1826. const bodyParams = {};
  1827. if (typeof quizId !== 'string') {
  1828. return new Error('"quizId" is a required parameter of type string');
  1829. }
  1830. if (typeof quizVersionId !== 'string') {
  1831. return new Error('"quizVersionId" is a required parameter of type string');
  1832. }
  1833. if (typeof quizSessionId !== 'string') {
  1834. return new Error('"quizSessionId" is a required parameter of type string');
  1835. }
  1836. if (typeof itemId !== 'string' && typeof itemName !== 'string') {
  1837. return new Error('"itemId" or "itemName" is a required parameter of type string');
  1838. }
  1839. bodyParams.quiz_id = quizId;
  1840. bodyParams.quiz_version_id = quizVersionId;
  1841. bodyParams.quiz_session_id = quizSessionId;
  1842. if (!helpers.isNil(itemId)) {
  1843. if (typeof itemId !== 'string') {
  1844. return new Error('"itemId" must be a string');
  1845. }
  1846. bodyParams.item_id = itemId;
  1847. }
  1848. if (!helpers.isNil(itemName)) {
  1849. if (typeof itemName !== 'string') {
  1850. return new Error('"itemName" must be a string');
  1851. }
  1852. bodyParams.item_name = itemName;
  1853. }
  1854. if (!helpers.isNil(variationId)) {
  1855. if (typeof variationId !== 'string') {
  1856. return new Error('"variationId" must be a string');
  1857. }
  1858. bodyParams.variation_id = variationId;
  1859. }
  1860. if (!helpers.isNil(section)) {
  1861. if (typeof section !== 'string') {
  1862. return new Error('"section" must be a string');
  1863. }
  1864. queryParams.section = section;
  1865. }
  1866. if (!helpers.isNil(resultCount)) {
  1867. if (typeof resultCount !== 'number') {
  1868. return new Error('"resultCount" must be a number');
  1869. }
  1870. bodyParams.result_count = resultCount;
  1871. }
  1872. if (!helpers.isNil(resultId)) {
  1873. if (typeof resultId !== 'string') {
  1874. return new Error('"resultId" must be a string');
  1875. }
  1876. bodyParams.result_id = resultId;
  1877. }
  1878. if (!helpers.isNil(resultPage)) {
  1879. if (typeof resultPage !== 'number') {
  1880. return new Error('"resultPage" must be a number');
  1881. }
  1882. bodyParams.result_page = resultPage;
  1883. }
  1884. if (!helpers.isNil(numResultsPerPage)) {
  1885. if (typeof numResultsPerPage !== 'number') {
  1886. return new Error('"numResultsPerPage" must be a number');
  1887. }
  1888. bodyParams.num_results_per_page = numResultsPerPage;
  1889. }
  1890. if (!helpers.isNil(resultPositionOnPage)) {
  1891. if (typeof resultPositionOnPage !== 'number') {
  1892. return new Error('"resultPositionOnPage" must be a number');
  1893. }
  1894. bodyParams.result_position_on_page = resultPositionOnPage;
  1895. }
  1896. const requestURL = `${requestPath}${applyParamsAsString(queryParams, this.options)}`;
  1897. const requestMethod = 'POST';
  1898. const requestBody = applyParams(bodyParams, { ...this.options, requestMethod });
  1899. this.requests.queue(
  1900. requestURL,
  1901. requestMethod,
  1902. requestBody,
  1903. networkParameters,
  1904. );
  1905. this.requests.send();
  1906. return true;
  1907. }
  1908. this.requests.send();
  1909. return new Error('parameters are required of type object');
  1910. }
  1911. /**
  1912. * Send quiz conversion event to API
  1913. *
  1914. * @function trackQuizConversion
  1915. * @param {object} parameters - Additional parameters to be sent with request
  1916. * @param {string} parameters.quizId - Quiz identifier
  1917. * @param {string} parameters.quizVersionId - Quiz version identifier
  1918. * @param {string} parameters.quizSessionId - Quiz session identifier associated with this conversion event
  1919. * @param {string} [parameters.itemId] - Product item unique identifier (Either itemId or itemName is required)
  1920. * @param {string} [parameters.itemName] - Product item name
  1921. * @param {string} [parameters.variationId] - Product item variation unique identifier
  1922. * @param {string} [parameters.revenue] - Sale price if available, otherwise the regular (retail) price of item
  1923. * @param {string} [parameters.section='Products'] - Index section
  1924. * @param {string} [parameters.type='add_to_cart'] - Conversion type
  1925. * @param {boolean} [parameters.isCustomType] - Specify if type is custom conversion type
  1926. * @param {string} [parameters.displayName] - Display name for the custom conversion type
  1927. * @param {object} [networkParameters] - Parameters relevant to the network request
  1928. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  1929. * @returns {(true|Error)}
  1930. * @description User viewed a quiz results page
  1931. * @example
  1932. * constructorio.tracker.trackQuizConversion(
  1933. * {
  1934. * quizId: 'coffee-quiz',
  1935. * quizVersionId: '1231244',
  1936. * quizSessionId: '3123',
  1937. * itemName: 'espresso',
  1938. * variationId: '167',
  1939. * type: 'add_to_cart',
  1940. * revenue: '1.0'
  1941. * },
  1942. * );
  1943. */
  1944. // eslint-disable-next-line complexity
  1945. trackQuizConversion(parameters, networkParameters = {}) {
  1946. // Ensure parameters are provided (required)
  1947. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  1948. const requestPath = `${this.options.serviceUrl}/v2/behavioral_action/quiz_conversion?`;
  1949. const {
  1950. quiz_id,
  1951. quizId = quiz_id,
  1952. quiz_version_id,
  1953. quizVersionId = quiz_version_id,
  1954. quiz_session_id,
  1955. quizSessionId = quiz_session_id,
  1956. item_id,
  1957. itemId = item_id,
  1958. item_name,
  1959. itemName = item_name,
  1960. variation_id,
  1961. variationId = variation_id,
  1962. revenue,
  1963. section = 'Products',
  1964. type,
  1965. is_custom_type,
  1966. isCustomType = is_custom_type,
  1967. display_name,
  1968. displayName = display_name,
  1969. } = parameters;
  1970. const queryParams = {};
  1971. const bodyParams = {};
  1972. // Ensure required parameters provided
  1973. if (typeof quizId !== 'string') {
  1974. return new Error('"quizId" is a required parameter of type string');
  1975. }
  1976. if (typeof quizVersionId !== 'string') {
  1977. return new Error('"quizVersionId" is a required parameter of type string');
  1978. }
  1979. if (typeof quizSessionId !== 'string') {
  1980. return new Error('"quizSessionId" is a required parameter of type string');
  1981. }
  1982. if (typeof itemId !== 'string' && typeof itemName !== 'string') {
  1983. return new Error('"itemId" or "itemName" is a required parameter of type string');
  1984. }
  1985. bodyParams.quiz_id = quizId;
  1986. bodyParams.quiz_version_id = quizVersionId;
  1987. bodyParams.quiz_session_id = quizSessionId;
  1988. if (!helpers.isNil(itemId)) {
  1989. if (typeof itemId !== 'string') {
  1990. return new Error('"itemId" must be a string');
  1991. }
  1992. bodyParams.item_id = itemId;
  1993. }
  1994. if (!helpers.isNil(itemName)) {
  1995. if (typeof itemName !== 'string') {
  1996. return new Error('"itemName" must be a string');
  1997. }
  1998. bodyParams.item_name = itemName;
  1999. }
  2000. if (!helpers.isNil(variationId)) {
  2001. if (typeof variationId !== 'string') {
  2002. return new Error('"variationId" must be a string');
  2003. }
  2004. bodyParams.variation_id = variationId;
  2005. }
  2006. if (!helpers.isNil(revenue)) {
  2007. if (typeof revenue !== 'string') {
  2008. return new Error('"revenue" must be a string');
  2009. }
  2010. bodyParams.revenue = revenue;
  2011. }
  2012. if (!helpers.isNil(section)) {
  2013. if (typeof section !== 'string') {
  2014. return new Error('"section" must be a string');
  2015. }
  2016. bodyParams.section = section;
  2017. }
  2018. if (!helpers.isNil(type)) {
  2019. if (typeof type !== 'string') {
  2020. return new Error('"type" must be a string');
  2021. }
  2022. bodyParams.type = type;
  2023. }
  2024. if (!helpers.isNil(isCustomType)) {
  2025. if (typeof isCustomType !== 'boolean') {
  2026. return new Error('"isCustomType" must be a boolean');
  2027. }
  2028. bodyParams.is_custom_type = isCustomType;
  2029. }
  2030. if (!helpers.isNil(displayName)) {
  2031. if (typeof displayName !== 'string') {
  2032. return new Error('"displayName" must be a string');
  2033. }
  2034. bodyParams.display_name = displayName;
  2035. }
  2036. const requestURL = `${requestPath}${applyParamsAsString(queryParams, this.options)}`;
  2037. const requestMethod = 'POST';
  2038. const requestBody = applyParams(bodyParams, { ...this.options, requestMethod });
  2039. this.requests.queue(
  2040. requestURL,
  2041. requestMethod,
  2042. requestBody,
  2043. networkParameters,
  2044. );
  2045. this.requests.send();
  2046. return true;
  2047. }
  2048. this.requests.send();
  2049. return new Error('parameters are required of type object');
  2050. }
  2051. /**
  2052. * Send ASA request submitted event
  2053. *
  2054. * @function trackAssistantSubmit
  2055. * @param {object} parameters - Additional parameters to be sent with request
  2056. * @param {string} parameters.intent - Intent of user request
  2057. * @param {string} [parameters.section] - The section name for the item Ex. "Products"
  2058. * @param {object} [networkParameters] - Parameters relevant to the network request
  2059. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  2060. * @returns {(true|Error)}
  2061. * @description User submitted an assistant search
  2062. * (pressing enter within assistant input element, or clicking assistant submit element)
  2063. * @example
  2064. * constructorio.tracker.trackAssistantSubmit(
  2065. * {
  2066. * intent: 'show me a recipe for a cookie',
  2067. * },
  2068. * );
  2069. */
  2070. trackAssistantSubmit(parameters, networkParameters = {}) {
  2071. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  2072. // Ensure parameters are provided (required)
  2073. const baseUrl = `${this.options.serviceUrl}/v2/behavioral_action/assistant_submit?`;
  2074. const {
  2075. section,
  2076. intent,
  2077. } = parameters;
  2078. const bodyParams = {
  2079. intent,
  2080. section,
  2081. };
  2082. const requestURL = `${baseUrl}${applyParamsAsString({}, this.options)}`;
  2083. const requestMethod = 'POST';
  2084. const requestBody = applyParams(bodyParams, {
  2085. ...this.options,
  2086. requestMethod,
  2087. });
  2088. this.requests.queue(
  2089. requestURL,
  2090. requestMethod,
  2091. requestBody,
  2092. networkParameters,
  2093. );
  2094. this.requests.send();
  2095. return true;
  2096. }
  2097. this.requests.send();
  2098. return new Error('parameters are required of type object');
  2099. }
  2100. /**
  2101. * Send assistant results page load started
  2102. *
  2103. * @function trackAssistantResultLoadStarted
  2104. * @param {object} parameters - Additional parameters to be sent with request
  2105. * @param {string} parameters.intent - Intent of user request
  2106. * @param {string} [parameters.section] - The section name for the item Ex. "Products"
  2107. * @param {string} [parameters.intentResultId] - The intent result id from the ASA response
  2108. * @param {object} [networkParameters] - Parameters relevant to the network request
  2109. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  2110. * @returns {(true|Error)}
  2111. * @description Assistant results page load begun (but has not necessarily loaded completely)
  2112. * @example
  2113. * constructorio.tracker.trackAssistantResultLoadStarted(
  2114. * {
  2115. * intent: 'show me a recipe for a cookie',
  2116. * intentResultId: 'Zde93fd-f955-4020-8b8d-6b21b93cb5a2',
  2117. * },
  2118. * );
  2119. */
  2120. trackAssistantResultLoadStarted(parameters, networkParameters = {}) {
  2121. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  2122. // Ensure parameters are provided (required)
  2123. const baseUrl = `${this.options.serviceUrl}/v2/behavioral_action/assistant_result_load_start?`;
  2124. const {
  2125. section,
  2126. intentResultId,
  2127. intent,
  2128. } = parameters;
  2129. const bodyParams = {
  2130. intent_result_id: intentResultId,
  2131. intent,
  2132. section,
  2133. };
  2134. const requestURL = `${baseUrl}${applyParamsAsString({}, this.options)}`;
  2135. const requestMethod = 'POST';
  2136. const requestBody = applyParams(bodyParams, {
  2137. ...this.options,
  2138. requestMethod,
  2139. });
  2140. this.requests.queue(
  2141. requestURL,
  2142. requestMethod,
  2143. requestBody,
  2144. networkParameters,
  2145. );
  2146. this.requests.send();
  2147. return true;
  2148. }
  2149. this.requests.send();
  2150. return new Error('parameters are required of type object');
  2151. }
  2152. /**
  2153. * Send assistant results page load finished
  2154. *
  2155. * @function trackAssistantResultLoadFinished
  2156. * @param {object} parameters - Additional parameters to be sent with request
  2157. * @param {string} parameters.intent - Intent of user request
  2158. * @param {number} parameters.searchResultCount - Number of search results loaded
  2159. * @param {string} [parameters.section] - The section name for the item Ex. "Products"
  2160. * @param {string} [parameters.intentResultId] - The intent result id from the ASA response
  2161. * @param {object} [networkParameters] - Parameters relevant to the network request
  2162. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  2163. * @returns {(true|Error)}
  2164. * @description Assistant results page load finished
  2165. * @example
  2166. * constructorio.tracker.trackAssistantResultLoadFinished(
  2167. * {
  2168. * intent: 'show me a recipe for a cookie',
  2169. * intentResultId: 'Zde93fd-f955-4020-8b8d-6b21b93cb5a2',
  2170. * searchResultCount: 5,
  2171. * },
  2172. * );
  2173. */
  2174. trackAssistantResultLoadFinished(parameters, networkParameters = {}) {
  2175. // Ensure parameters are provided (required)
  2176. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  2177. const baseUrl = `${this.options.serviceUrl}/v2/behavioral_action/assistant_result_load_finish?`;
  2178. const {
  2179. section,
  2180. searchResultCount,
  2181. intentResultId,
  2182. intent,
  2183. } = parameters;
  2184. const bodyParams = {
  2185. intent_result_id: intentResultId,
  2186. section,
  2187. intent,
  2188. search_result_count: searchResultCount,
  2189. };
  2190. const requestURL = `${baseUrl}${applyParamsAsString({}, this.options)}`;
  2191. const requestMethod = 'POST';
  2192. const requestBody = applyParams(bodyParams, {
  2193. ...this.options,
  2194. requestMethod,
  2195. });
  2196. this.requests.queue(
  2197. requestURL,
  2198. requestMethod,
  2199. requestBody,
  2200. networkParameters,
  2201. );
  2202. this.requests.send();
  2203. return true;
  2204. }
  2205. this.requests.send();
  2206. return new Error('parameters are required of type object');
  2207. }
  2208. /**
  2209. * Send assistant result click event to API
  2210. *
  2211. * @function trackAssistantResultClick
  2212. * @param {object} parameters - Additional parameters to be sent with request
  2213. * @param {string} parameters.intent - intent of the user
  2214. * @param {string} parameters.searchResultId - result_id of the specific search result the clicked item belongs to
  2215. * @param {string} parameters.itemId - Product item unique identifier
  2216. * @param {string} parameters.itemName - Product item name
  2217. * @param {string} [parameters.section] - The section name for the item Ex. "Products"
  2218. * @param {string} [parameters.variationId] - Product item variation unique identifier
  2219. * @param {string} [parameters.intentResultId] - Browse result identifier (returned in response from Constructor)
  2220. * @param {object} [networkParameters] - Parameters relevant to the network request
  2221. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  2222. * @returns {(true|Error)}
  2223. * @description User clicked a result that appeared within an assistant search result
  2224. * @example
  2225. * constructorio.tracker.trackAssistantResultClick(
  2226. * {
  2227. * variationId: 'KMH879-7632',
  2228. * searchResultId: '019927c2-f955-4020-8b8d-6b21b93cb5a2',
  2229. * intentResultId: 'Zde93fd-f955-4020-8b8d-6b21b93cb5a2',
  2230. * intent: 'show me a recipe for a cookie',
  2231. * itemId: 'KMH876',
  2232. * },
  2233. * );
  2234. */
  2235. trackAssistantResultClick(parameters, networkParameters = {}) {
  2236. // Ensure parameters are provided (required)
  2237. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  2238. const requestPath = `${this.options.serviceUrl}/v2/behavioral_action/assistant_search_result_click?`;
  2239. const {
  2240. section = 'Products',
  2241. variationId,
  2242. intentResultId,
  2243. searchResultId,
  2244. itemId,
  2245. itemName,
  2246. intent,
  2247. } = parameters;
  2248. const bodyParams = {
  2249. section,
  2250. variation_id: variationId,
  2251. intent_result_id: intentResultId,
  2252. search_result_id: searchResultId,
  2253. item_id: itemId,
  2254. item_name: itemName,
  2255. intent,
  2256. };
  2257. const requestURL = `${requestPath}${applyParamsAsString({}, this.options)}`;
  2258. const requestMethod = 'POST';
  2259. const requestBody = applyParams(bodyParams, { ...this.options, requestMethod });
  2260. this.requests.queue(
  2261. requestURL,
  2262. requestMethod,
  2263. requestBody,
  2264. networkParameters,
  2265. );
  2266. this.requests.send();
  2267. return true;
  2268. }
  2269. this.requests.send();
  2270. return new Error('parameters are required of type object');
  2271. }
  2272. /**
  2273. * Send assistant search result view event to API
  2274. *
  2275. * @function trackAssistantResultView
  2276. * @param {object} parameters - Additional parameters to be sent with request
  2277. * @param {string} parameters.intent - intent of the user
  2278. * @param {string} parameters.searchResultId - result_id of the specific search result the clicked item belongs to
  2279. * @param {number} parameters.numResultsViewed - Number of items viewed in this search result
  2280. * @param {object[]} [parameters.items] - List of product item objects viewed
  2281. * @param {string} [parameters.section] - The section name for the item Ex. "Products"
  2282. * @param {string} [parameters.intentResultId] - Browse result identifier (returned in response from Constructor)
  2283. * @param {object} [networkParameters] - Parameters relevant to the network request
  2284. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  2285. * @returns {(true|Error)}
  2286. * @description User viewed a search result within an assistant result
  2287. * @example
  2288. * constructorio.tracker.trackAssistantResultView(
  2289. * {
  2290. * searchResultId: '019927c2-f955-4020-8b8d-6b21b93cb5a2',
  2291. * intentResultId: 'Zde93fd-f955-4020-8b8d-6b21b93cb5a2',
  2292. * intent: 'show me a recipe for a cookie',
  2293. * numResultsViewed: 5,
  2294. * items: [{itemId: 'KMH876'}, {itemId: 'KMH140'}, {itemId: 'KMH437'}],
  2295. * },
  2296. * );
  2297. */
  2298. trackAssistantResultView(parameters, networkParameters = {}) {
  2299. // Ensure parameters are provided (required)
  2300. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  2301. const requestPath = `${this.options.serviceUrl}/v2/behavioral_action/assistant_search_result_view?`;
  2302. const {
  2303. section = 'Products',
  2304. items,
  2305. numResultsViewed,
  2306. intentResultId,
  2307. searchResultId,
  2308. intent,
  2309. } = parameters;
  2310. const bodyParams = {
  2311. section,
  2312. intent_result_id: intentResultId,
  2313. search_result_id: searchResultId,
  2314. num_results_viewed: numResultsViewed,
  2315. items: items && Array.isArray(items) && items.slice(0, 100).map((item) => helpers.toSnakeCaseKeys(item, false)),
  2316. intent,
  2317. };
  2318. const requestURL = `${requestPath}${applyParamsAsString({}, this.options)}`;
  2319. const requestMethod = 'POST';
  2320. const requestBody = applyParams(bodyParams, { ...this.options, requestMethod });
  2321. this.requests.queue(
  2322. requestURL,
  2323. requestMethod,
  2324. requestBody,
  2325. networkParameters,
  2326. );
  2327. this.requests.send();
  2328. return true;
  2329. }
  2330. this.requests.send();
  2331. return new Error('parameters are required of type object');
  2332. }
  2333. /**
  2334. * Send ASA search submitted event
  2335. *
  2336. * @function trackAssistantSearchSubmit
  2337. * @param {object} parameters - Additional parameters to be sent with request
  2338. * @param {string} parameters.intent - Intent of user request
  2339. * @param {string} parameters.searchTerm - Term of submitted assistant search event
  2340. * @param {string} parameters.searchResultId - resultId of search result the clicked item belongs to
  2341. * @param {string} [parameters.section] - The section name for the item Ex. "Products"
  2342. * @param {string} [parameters.intentResultId] - intentResultId from the ASA response
  2343. * @param {object} [networkParameters] - Parameters relevant to the network request
  2344. * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
  2345. * @returns {(true|Error)}
  2346. * @description User submitted an alternative assistant search result search term
  2347. * @example
  2348. * constructorio.tracker.trackAssistantSearchSubmit({
  2349. * {
  2350. * searchResultId: '019927c2-f955-4020-8b8d-6b21b93cb5a2',
  2351. * intentResultId: 'Zde93fd-f955-4020-8b8d-6b21b93cb5a2',
  2352. * intent: 'show me a recipe for a cookie',
  2353. * searchTerm: 'flour',
  2354. * },
  2355. * );
  2356. */
  2357. trackAssistantSearchSubmit(parameters, networkParameters = {}) {
  2358. // Ensure parameters are provided (required)
  2359. if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
  2360. // Ensure parameters are provided (required)
  2361. const baseUrl = `${this.options.serviceUrl}/v2/behavioral_action/assistant_search_submit?`;
  2362. const {
  2363. section,
  2364. intent,
  2365. searchTerm,
  2366. searchResultId,
  2367. intentResultId,
  2368. } = parameters;
  2369. const bodyParams = {
  2370. intent,
  2371. section,
  2372. search_term: searchTerm,
  2373. search_result_id: searchResultId,
  2374. intent_result_id: intentResultId,
  2375. };
  2376. const requestURL = `${baseUrl}${applyParamsAsString({}, this.options)}`;
  2377. const requestMethod = 'POST';
  2378. const requestBody = applyParams(bodyParams, {
  2379. ...this.options,
  2380. requestMethod,
  2381. });
  2382. this.requests.queue(
  2383. requestURL,
  2384. requestMethod,
  2385. requestBody,
  2386. networkParameters,
  2387. );
  2388. this.requests.send();
  2389. return true;
  2390. }
  2391. this.requests.send();
  2392. return new Error('parameters is a required parameter of type object');
  2393. }
  2394. /**
  2395. * Subscribe to success or error messages emitted by tracking requests
  2396. *
  2397. * @function on
  2398. * @param {string} messageType - Type of message to listen for ('success' or 'error')
  2399. * @param {function} callback - Callback to be invoked when message received
  2400. * @returns {(true|Error)}
  2401. * @example
  2402. * constructorio.tracker.on('error', (data) => {
  2403. * // Handle tracking error
  2404. * });
  2405. */
  2406. on(messageType, callback) {
  2407. if (messageType !== 'success' && messageType !== 'error') {
  2408. return new Error('messageType must be a string of value "success" or "error"');
  2409. }
  2410. if (!callback || typeof callback !== 'function') {
  2411. return new Error('callback is required and must be a function');
  2412. }
  2413. this.eventemitter.on(messageType, callback);
  2414. return true;
  2415. }
  2416. }
  2417. // Exposed for testing
  2418. Tracker.RequestQueue = RequestQueue;
  2419. module.exports = Tracker;