UNPKG

facebook-nodejs-business-sdk

Version:
1,816 lines (1,498 loc) 1.21 MB
define(['exports', 'fs', 'path'], function (exports, fs, path) { 'use strict'; fs = 'default' in fs ? fs['default'] : fs; path = 'default' in path ? path['default'] : path; /** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. * * @format */ // HTTP Status Code var HTTP_STATUS = { OK: '200', NOT_MODIFIED: '304' }; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; var createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var get$1 = function get$1(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get$1(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; var inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }; var possibleConstructorReturn = function (self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }; var set$1 = function set$1(object, property, value, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent !== null) { set$1(parent, property, value, receiver); } } else if ("value" in desc && desc.writable) { desc.value = value; } else { var setter = desc.set; if (setter !== undefined) { setter.call(receiver, value); } } return value; }; var toConsumableArray = function (arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } }; /** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. * * @format */ var _requestPromise = require('request-promise'); /** * Isomorphic Http Promise Requests Class * */ var Http = function () { function Http() { classCallCheck(this, Http); } createClass(Http, null, [{ key: 'request', /** * Request * @param {String} method * @param {String} url * @param {Object} [data] * @return {Promise} */ value: function request(method, url, data, files, useMultipartFormData, showHeader) { if (typeof window !== 'undefined' && window.XMLHttpRequest) { return Http.xmlHttpRequest(method, url, data); } return Http.requestPromise(method, url, data, files, useMultipartFormData, showHeader); } /** * XmlHttpRequest request * @param {String} method * @param {String} url * @param {Object} [data] * @return {Promise} */ }, { key: 'xmlHttpRequest', value: function xmlHttpRequest(method, url, data) { return new Promise(function (resolve, reject) { var request = new window.XMLHttpRequest(); request.open(method, url); request.onload = function () { try { var response = JSON.parse(request.response); if (request.status.toString() === HTTP_STATUS.OK) { resolve(response); } else { reject(new Error({ body: response, status: request.status })); } } catch (e) { reject(new Error({ body: request.responseText, status: request.status })); } }; request.setRequestHeader('Content-Type', 'application/json'); request.setRequestHeader('Accept', 'application/json'); request.send(JSON.stringify(data)); }); } /** * Request Promise * @param {String} method The HTTP method name (e.g. 'GET'). * @param {String} url A full URL string. * @param {Object} [data] A mapping of request parameters where a key * is the parameter name and its value is a string or an object * which can be JSON-encoded. * @param {Object} [files] An optional mapping of file names to ReadStream * objects. These files will be attached to the request. * @param {Boolean} [useMultipartFormData] An optional flag to call with * multipart/form-data. * @return {Promise} */ }, { key: 'requestPromise', value: function requestPromise(method, url, data, files) { var useMultipartFormData = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; var showHeader = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false; var options = { method: method, uri: url, json: !useMultipartFormData, headers: { 'User-Agent': 'fbbizsdk-nodejs-' + FacebookAdsApi.VERSION }, body: Object, resolveWithFullResponse: showHeader }; // Prevent null or undefined input // because it can be merged with the files argument later if (!data) { data = {}; } options.body = data; // Handle file attachments if provided if (useMultipartFormData || files && Object.keys(files).length > 0) { // Use formData instead of body (required by the request-promise library) options.formData = Object.assign(data, files); delete options.body; } return _requestPromise(options).catch(function (response) { throw response; }); } }]); return Http; }(); /** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. * * @format */ // request-promise error types var REQUEST_ERROR = 'RequestError'; var STATUS_CODE_ERROR = 'StatusCodeError'; function FacebookError(error) { this.name = 'FacebookError'; this.message = error.message; this.stack = new Error().stack; } FacebookError.prototype = Object.create(Error.prototype); FacebookError.prototype.constructor = FacebookError; /** * Raised when an api request fails. */ var FacebookRequestError = function (_FacebookError) { inherits(FacebookRequestError, _FacebookError); /** * @param {[Object} response * @param {String} method * @param {String} url * @param {Object} data */ function FacebookRequestError(response, method, url, data) { classCallCheck(this, FacebookRequestError); var errorResponse = constructErrorResponse(response); var _this = possibleConstructorReturn(this, (FacebookRequestError.__proto__ || Object.getPrototypeOf(FacebookRequestError)).call(this, errorResponse)); _this.name = 'FacebookRequestError'; _this.message = errorResponse.message; _this.status = errorResponse.status; _this.response = errorResponse.body; _this.headers = errorResponse.headers; _this.method = method; _this.url = url; if (data) { _this.data = data; } return _this; } return FacebookRequestError; }(FacebookError); /** * Error response has several structures depended on called APIs or errors. * This method contructs and formats the response into the same structure for * creating a FacebookRequestError object. */ function constructErrorResponse(response) { var body = void 0; var message = void 0; var status = void 0; var headers = void 0; // Batch request error contains code and body fields var isBatchResponse = response.code && response.body; if (isBatchResponse) { // Handle batch response body = typeof response.body === 'string' ? JSON.parse(response.body) : response.body; status = response.code; message = body.error.message; headers = response.headers; } else { // Handle single response if (response.name === STATUS_CODE_ERROR) { // Handle when we can get response error code body = response.error ? response.error : response; body = typeof body === 'string' ? JSON.parse(body) : body; // Construct an error message from subfields in body.error message = body.error.error_user_msg ? body.error.error_user_title + ': ' + body.error.error_user_msg : body.error.message; status = response.statusCode; if (response.response) { headers = response.response.headers; } } else if (response.name === REQUEST_ERROR) { // Handle network errors e.g. timeout, destination unreachable body = { error: response.error }; // An error message is in the response already message = response.message; // Network errors have no status code status = null; } } return { body: body, message: message, status: status, headers: headers }; } /** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. * * @format */ var CrashReporter = function () { function CrashReporter() { classCallCheck(this, CrashReporter); this._active = true; } createClass(CrashReporter, null, [{ key: 'enable', value: function enable() { var _this = this; if (this._instance == undefined || this._instance == null) { this._instance = new this(); process.on('uncaughtException', function (err) { if (_this._instance._active && err instanceof Error) { var params = privateMethods.parseParam(err); if (params != null) { console.log('CrashReporter: SDK crash detected!'); privateMethods.processUncaughtException(err, params); return; } } console.log('CrashReporter: No SDK crash detected or crash reporter is disabled!'); throw err; }); } } }, { key: 'disable', value: function disable() { if (this._instance == undefined || this._instance == null) { return; } this._instance._active = false; } }]); return CrashReporter; }(); var privateMethods = { processUncaughtException: function processUncaughtException(err, params) { FacebookAdsApi.getDefaultApi().getAppID().then(function (data) { if (data["data"] !== undefined && data['data']['app_id'] !== undefined) { var appID = data['data']['app_id']; console.log("active uncaughtException : " + appID); var url = [FacebookAdsApi.GRAPH, FacebookAdsApi.VERSION, appID, 'instruments'].join('/'); Http.request('POST', url, params).then(function (response) { console.log('Successfully sent crash report.'); }).catch(function (response) { console.log('Failed to send crash report.'); }).then(function () { throw err; }); } }).catch(function (error) { console.log("Not be able to find appID, fail to send report to server."); throw err; }); }, parseParam: function parseParam(err) { var stack = err.stack.split('\n'); var params = {}; if (stack.length == 0) { return null; } var fln = stack[0].split(':'); params['reason'] = fln[0]; params['callstack'] = stack; params['platform'] = process.version; for (var i = 0; i < stack.length; i++) { if (stack[i].includes('facebook-nodejs-business-sdk')) { return { 'bizsdk_crash_report': params }; } } return null; } }; /** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. * * @format */ /** * Facebook Ads API */ var FacebookAdsApi = function () { createClass(FacebookAdsApi, null, [{ key: 'VERSION', get: function get() { return 'v5.0'; } }, { key: 'GRAPH', get: function get() { return 'https://graph.facebook.com'; } }, { key: 'GRAPH_VIDEO', get: function get() { return 'https://graph-video.facebook.com'; } /** * @param {String} accessToken * @param {String} [locale] */ }]); function FacebookAdsApi(accessToken) { var locale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'en_US'; var crash_log = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; classCallCheck(this, FacebookAdsApi); if (!accessToken) { throw new Error('Access token required'); } this.accessToken = accessToken; this.locale = locale; this._debug = false; this._showHeader = false; if (crash_log) { CrashReporter.enable(); } } /** * Instantiate an API and store it as the default * @param {String} accessToken * @param {String} [locale] * @return {FacebookAdsApi} */ createClass(FacebookAdsApi, [{ key: 'getAppID', value: function getAppID() { var url = [FacebookAdsApi.GRAPH, FacebookAdsApi.VERSION, 'debug_token'].join('/'); var params = {}; params['access_token'] = this.accessToken; params['input_token'] = this.accessToken; params['fields'] = 'app_id'; url += '?' + FacebookAdsApi._encodeParams(params); return Http.request('GET', url, {}, {}, false); } }, { key: 'setDebug', value: function setDebug(flag) { this._debug = flag; return this; } }, { key: 'setShowHeader', value: function setShowHeader(flag) { this._showHeader = flag; return this; } /** * Http Request * @param {String} method * @param {String} path * @param {Object} [params] * @param {Object} [files] * @return {Promise} */ }, { key: 'call', value: function call(method, path$$1) { var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var files = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; var _this = this; var useMultipartFormData = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; var urlOverride = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : ''; var url = void 0; var data = {}; if (method === 'POST' || method === 'PUT') { data = params; params = {}; } var domain = urlOverride || FacebookAdsApi.GRAPH; if (typeof path$$1 !== 'string' && !(path$$1 instanceof String)) { url = [domain, FacebookAdsApi.VERSION].concat(toConsumableArray(path$$1)).join('/'); params['access_token'] = this.accessToken; url += '?' + FacebookAdsApi._encodeParams(params); } else { url = path$$1; } var strUrl = url; return Http.request(method, strUrl, data, files, useMultipartFormData, this._showHeader).then(function (response) { if (_this._showHeader) { response.body['headers'] = response.headers; response = response.body; } if (_this._debug) { console.log('200 ' + method + ' ' + url + ' ' + (Object.keys(data).length > 0 ? JSON.stringify(data) : "")); console.log('Response: ' + (response ? JSON.stringify(response) : "")); } return Promise.resolve(response); }).catch(function (response) { if (_this._debug) { console.log(response.statusCode + ' ' + method + ' ' + url + '\n ' + (Object.keys(data).length > 0 ? JSON.stringify(data) : '')); } throw new FacebookRequestError(response, method, url, data); }); } }], [{ key: 'init', value: function init(accessToken) { var locale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'en_US'; var crash_log = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; var api = new this(accessToken, locale, crash_log); this.setDefaultApi(api); return api; } }, { key: 'setDefaultApi', value: function setDefaultApi(api) { this._defaultApi = api; } }, { key: 'getDefaultApi', value: function getDefaultApi() { return this._defaultApi; } }, { key: '_encodeParams', value: function _encodeParams(params) { return Object.keys(params).map(function (key) { var param = params[key]; if ((typeof param === 'undefined' ? 'undefined' : _typeof(param)) === 'object') { param = param ? JSON.stringify(param) : ''; } return encodeURIComponent(key) + '=' + encodeURIComponent(param); }).join('&'); } }]); return FacebookAdsApi; }(); /** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. * * @format */ /** * Represents an API request */ var APIRequest = function () { /** * @param {string} nodeId The node id to perform the api call. * @param {string} method The HTTP method of the call. * @param {string} endpoint The edge of the api call. */ function APIRequest(nodeId, method, endpoint) { classCallCheck(this, APIRequest); this._nodeId = nodeId; this._method = method; this._endpoint = endpoint.replace('/', ''); this._path = [nodeId, this.endpoint]; this._fields = []; this._fileParams = Object.create(null); this._params = Object.create(null); this._fileCounter = 0; } /** * Getter function for node ID * @return {string} Node ID */ createClass(APIRequest, [{ key: 'addFile', /** * @param {string} filePath Path to file attached to the request * @return {APIReqeust} APIRequest instance */ value: function addFile(filePath) { var fileKey = 'source' + this._fileCounter; var stats = fs.lstatSync(filePath); if (!stats.isFile()) { throw Error('Cannot find file ' + filePath + '!'); } this._fileParams[fileKey] = filePath; this._fileCounter += 1; return this; } /** * @param {string[]} filePaths Array of paths to files attached to the request * @return {APIRequest} APIRequest instance */ }, { key: 'addFiles', value: function addFiles(filePaths) { var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = filePaths[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var filePath = _step.value; this.addFile(filePath); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } return this; } /** * @param {string} field Requested field * @return {APIReqeust} APIRequest instance */ }, { key: 'addField', value: function addField(field) { if (!this._fields.includes(field)) { this._fields.push(field); } return this; } /** * @param {string[]} fields Array of requested fields * @return {APIRequest} APIRequest instance */ }, { key: 'addFields', value: function addFields(fields) { var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = fields[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var field = _step2.value; this.addField(field); } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } return this; } /** * @param {string} key Param key * @param {*} value Param value * @return {APIRequest} APIRequest instance */ }, { key: 'addParam', value: function addParam(key, value) { this._params[key] = value; return this; } /** * @param {Object} params An object containing param keys and values * @return {APIRequest} APIRequest instance */ }, { key: 'addParams', value: function addParams(params) { this._params = params; return this; } }, { key: 'nodeId', get: function get() { return this._nodeId; } /** * Getter function for HTTP method e.g. GET, POST * @return {string} HTTP method */ }, { key: 'method', get: function get() { return this._method; } /** * Getter function for the edge of the API call * @return {string} Endpoint edge */ }, { key: 'endpoint', get: function get() { return this._endpoint; } /** * Getter function for path tokens * @return {Array<string>} Array of path tokens */ }, { key: 'path', get: function get() { return this._path; } /** * Getter function for requested fields * @return {Array<string>} Array of request fields */ }, { key: 'fields', get: function get() { return this._fields; } /** * Getter function for API params * @return {Object} Object containing API Params */ }, { key: 'params', get: function get() { // Deep cloning when object value is not a function return JSON.parse(JSON.stringify(this._params)); } /** * Getter function for API fileparams * @return {Object} Object containing API fileParams */ }, { key: 'fileParams', get: function get() { // Deep cloning when object value is not a function return JSON.parse(JSON.stringify(this._fileParams)); } }]); return APIRequest; }(); /** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. * * @format */ /** * Encapsulates an http response from Facebook's Graph API. */ var APIResponse = function () { function APIResponse(response, call) { classCallCheck(this, APIResponse); response.body = JSON.parse(response.body); this._body = response.body; this._httpStatus = response.code; this._headers = response.headers; this._call = call; this._response = response; } /** * @return {Object} The response body */ createClass(APIResponse, [{ key: 'body', get: function get() { return this._body; } }, { key: 'headers', get: function get() { return this._headers; } }, { key: 'etag', get: function get() { return this._headers['ETag']; } }, { key: 'status', get: function get() { return this._httpStatus; } }, { key: 'isSuccess', get: function get() { var body = this._body; if ('error' in body) { return false; } else if (Object.keys(body).length !== 0) { if ('success' in body) { return body['success']; } return !('Service Unavailable' in body); } else if (this._httpStatus === HTTP_STATUS.NOT_MODIFIED) { // ETag Hit return true; } else if (this._httpStatus === HTTP_STATUS.OK) { // HTTP OK return true; } else { // Something else return false; } } }, { key: 'error', get: function get() { if (this.isSuccess) { return null; } return new FacebookRequestError(this._response, this._call.method, this._call.relativeUrl, this._call.body); } }]); return APIResponse; }(); /** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. * * @format */ /** * Facebook Ads API Batch */ var FacebookAdsApiBatch = function () { /** * @param {FacebookAdsApi} api * @param {Function} successCallback * @param {Function} failureCallback */ function FacebookAdsApiBatch(api, successCallback, failureCallback) { classCallCheck(this, FacebookAdsApiBatch); this._api = api; this._files = []; this._batch = []; this._requests = []; this._successCallbacks = []; this._failureCallbacks = []; if (successCallback != null) { this._successCallbacks.push(successCallback); } if (failureCallback != null) { this._failureCallbacks.push(failureCallback); } } /** * Adds a call to the batch. * @param {string} method The HTTP method name (e.g. 'GET'). * @param {string[]|string} relativePath An array of path tokens or * a relative URL string. An array will be translated to a url as follows: * <graph url>/<tuple[0]>/<tuple[1]>... * It will be assumed that if the path is not a string, it will be iterable. * @param {Object} [params] A mapping of request parameters * where a key is the parameter name and its value is a string or an object * which can be JSON-encoded. * @param {Object} [files] An optional mapping of file names to binary open * file objects. These files will be attached to the request. * @param {Function} [successCallback] A callback function which will be * called with the response of this call if the call succeeded. * @param {Function} [failureCallback] A callback function which will be * called with the response of this call if the call failed. * @param {APIRequest} [request] The APIRequest object * @return {Object} An object describing the call */ createClass(FacebookAdsApiBatch, [{ key: 'add', value: function add(method, relativePath, params, files, successCallback, failureCallback, request) { // Construct a relaitveUrl from relateivePath by assuming that // relativePath can only be a string or an array of strings var relativeUrl = typeof relativePath === 'string' ? relativePath : relativePath.join('/'); // Contruct key-value pairs from params for GET querystring or POST body if (params != null) { var keyVals = []; for (var key in params) { var value = params[key]; if (_typeof(params[key]) === 'object' && !(params[key] instanceof Date)) { value = JSON.stringify(value); } keyVals.push(key + '=' + value); } if (method === 'GET') { relativeUrl += '?' + keyVals.join('&'); } else { var body = keyVals.join('&'); } if (params && params['name']) { var name = params['name']; } } // Handle attached files if (files != null) { var attachedFiles = Object.keys(files).join(','); } // A Call object that will be used in a batch request var call = { method: method, relative_url: relativeUrl, body: body, name: name, attachedFiles: attachedFiles }; this._batch.push(call); this._files.push(files); this._successCallbacks.push(successCallback); this._failureCallbacks.push(failureCallback); if (request !== undefined) { this._requests.push(request); } return call; } /** * Interface to add a APIRequest to the batch. * @param {APIRequest} request The APIRequest object to add * @param {Function} [successCallback] A callback function which * will be called with response of this call if the call succeeded. * @param {Function} [failureCallback] A callback function which * will be called with the FacebookResponse of this call if the call failed. * @return {Object} An object describing the call */ }, { key: 'addRequest', value: function addRequest(request, successCallback, failureCallback) { var updatedParams = request.params; updatedParams['fields'] = request.fields.join(); return this.add(request.method, request.path, updatedParams, request.fileParams, successCallback, failureCallback, request); } /** * Makes a batch call to the api associated with this object. * For each individual call response, calls the success or failure callback * function if they were specified. * Note: Does not explicitly raise exceptions. Individual exceptions won't * be thrown for each call that fails. The success and failure callback * functions corresponding to a call should handle its success or failure. * @return {FacebookAdsApiBatch|None} If some of the calls have failed, * returns a new FacebookAdsApiBatch object with those calls. * Otherwise, returns None. */ }, { key: 'execute', value: function execute() { var _this = this; if (this._batch.length < 1) { return; } var method = 'POST'; var path$$1 = []; // request to root domain for a batch request var params = { batch: this._batch }; // Call to the batch endpoint (WIP) return this._api.call(method, path$$1, params).then(function (responses) { // Keep track of batch indices that need to retry var retryIndices = []; // Check each response for (var index = 0; index < responses.length; index++) { var response = responses[index]; if (response != null) { var apiResponse = new APIResponse(response, _this._batch[index]); // Call the success callback if provided if (apiResponse.isSuccess) { if (_this._successCallbacks[index]) { _this._successCallbacks[index](apiResponse); } } else { // Call the failure callback if provided if (_this._failureCallbacks[index]) { _this._failureCallbacks[index](apiResponse); } } } else { // Do not get response, so, we keep track of the index to retry retryIndices.push(index); } } // Create and return new batch if we need to retry if (retryIndices.length > 0) { // Create a new batch from retry indices in the current batch var newBatch = new FacebookAdsApiBatch(_this._api); var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = retryIndices[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var _index = _step.value; newBatch._files.push(_this._files[_index]); newBatch._batch.push(_this._batch[_index]); newBatch._successCallbacks.push(_this._successCallbacks[_index]); newBatch._failureCallbacks.push(_this._failureCallbacks[_index]); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } return newBatch; } // No retry return null; }).catch(function (error) { throw error; }); } }]); return FacebookAdsApiBatch; }(); /** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. * */ /** * Content is part of the Custom Data Parameters of a Server Side Event Request. Content can be used to set the item/product details added in the Custom Data. * @see {@link https://developers.facebook.com/docs/marketing-api/facebook-pixel/server-side-api/parameters#custom} */ var Content = function () { /** * @param {String} id Product Id of the Item. * @param {Number} quantity Quantity of the Item. * @param {Number} item_price Price per unit of the content/product. */ function Content(id, quantity, item_price) { classCallCheck(this, Content); this._id = id; this._quantity = quantity; this._item_price = item_price; } /** * Gets the Product Id of the Item. * A string representing the unique Id for the product. * Example: XYZ. */ createClass(Content, [{ key: 'setId', /** * Sets the Product Id of the Item. * @param id is a string representing the unique id for the product. * Example: XYZ. */ value: function setId(id) { this._id = id; return this; } /** * Gets the quantity of the Item. * The number/quantity of the content that is being involved in the customer interaction. * Example: 5 */ }, { key: 'setQuantity', /** * Sets the quantity of the Content/Item. * @param {Number} quantity The number/quantity of the product that is being involved in the customer interaction. * Example: 5 */ value: function setQuantity(quantity) { this._quantity = quantity; return this; } /** * Gets the item price for the Product. * The item_price or price per unit of the product. * Example: '123.45' */ }, { key: 'setItemPrice', /** * Sets the item price for the Content. * @param {Number} item_price The item_price or price per unit of the product. * Example: '123.45' */ value: function setItemPrice(item_price) { this._item_price = item_price; return this; } /** * Returns the normalized payload for the Content. * @returns {Object} normalized Content payload. */ }, { key: 'normalize', value: function normalize() { var content = {}; if (this.id) { content['id'] = this.id; } if (this.quantity) { content['quantity'] = this.quantity; } if (this.item_price) { content['item_price'] = this.item_price; } return content; } }, { key: 'id', get: function get() { return this._id; } /** * Sets the Product Id of the Item. * @param id A string representing the unique Id for the product. * Example: XYZ. */ , set: function set(id) { this._id = id; } }, { key: 'quantity', get: function get() { return this._quantity; } /** * Sets the quantity of the Item. * @param quantity The number/quantity of the product that is being involved in the customer interaction. * Example: 5 */ , set: function set(quantity) { this._quantity = quantity; } }, { key: 'item_price', get: function get() { return this._item_price; } /** * Sets the item price for the Content. * @param item_price The item_price or price per unit of the product. * Example: '123.45' */ , set: function set(item_price) { this._item_price = item_price; } }]); return Content; }(); /** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. * */ var sha256 = require('js-sha256'); var PHONE_NUMBER_IGNORE_CHAR_SET = /[\-@#<>'",; ]|\(|\)|\+|[a-z]/g; var PHONE_NUMBER_DROP_PREFIX_ZEROS = /^\+?0{0,2}/; var US_PHONE_NUMBER_REGEX = /^1\(?\d{3}\)?\d{7}$/; var INTL_PHONE_NUMBER_REGEX = /^\d{1,4}\(?\d{2,3}\)?\d{4,}$/; /** * ServerSideUtils contains the Utility modules used for sending Server Side Events */ var ServerSideUtils = function () { function ServerSideUtils() { classCallCheck(this, ServerSideUtils); } createClass(ServerSideUtils, null, [{ key: 'normalizeAndHash', /** * Normalizes and hashes the input given the field name. * @param {String} [input] Value to be normalized. eg: `foo@bar.com` for email input. * @param {String} [field] Key(Type) of Value to be normalized eg: 'em' for email field. * @return {String} Normalized and hashed value for the string. */ value: function normalizeAndHash(input, field) { if (field == null || input == null) { return null; } var normalized_input = input.trim().toLowerCase(); if (normalized_input.length == 0) { return null; } switch (field) { case 'country': normalized_input = ServerSideUtils.normalizeCountry(normalized_input); break; case 'ct': normalized_input = ServerSideUtils.normalizeCity(normalized_input); break; case 'em': normalized_input = ServerSideUtils.normalizeEmail(normalized_input); break; case 'ge': normalized_input = ServerSideUtils.normalizeGender(normalized_input); break; case 'ph': normalized_input = ServerSideUtils.normalizePhone(normalized_input); break; case 'st': normalized_input = ServerSideUtils.normalizeState(normalized_input); break; case 'zp': normalized_input = ServerSideUtils.normalizeZip(normalized_input); break; } // Hashing the normalized input with SHA 256 var hashed_input = ServerSideUtils.tosha256(normalized_input); return hashed_input; } /** * Normalizes the given country token and returns acceptable two letter ISO country code * @param {String} [country] country value to be normalized. * @return {String} Normalized ISO country code. */ }, { key: 'normalizeCountry', value: function normalizeCountry(country) { // Retain only alpha characters bounded for ISO code. country = country.replace(/[^a-z]/g, ''); if (country.length != 2) { throw new Error("Invalid format for country:'" + country + "'.Please follow ISO 3166-1 2-letter standard for representing country. eg: us"); } return country; } /** * Normalizes the given city and returns acceptable city value * @param {String} [city] city value to be normalized. * @return {String} Normalized city value. */ }, { key: 'normalizeCity', value: function normalizeCity(city) { city = city.replace(/[0-9\s().-]/g, ''); return city; } /** * Normalizes the given currency string and returns acceptable three letter ISO code * @param {String} [currency] country value to be normalized. * @return {String} Normalized ISO currency code. */ }, { key: 'normalizeCurrency', value: function normalizeCurrency(currency) { // Retain only alpha characters bounded for ISO code. currency = currency.replace(/[^a-z]/g, ''); if (currency.length != 3) { throw new Error("Invalid format for currency:'" + currency + "'.Please follow ISO 4217 3-letter standard for representing currency. Eg: usd"); } return currency; } /** * Normalizes the given email to RFC 822 standard and returns acceptable email value * @param {String} [email] email value to be normalized. * @return {String} Normalized email value. */ }, { key: 'normalizeEmail', value: function normalizeEmail(email) { // RFC 2822 REGEX approximation var EMAIL_RE = /^[\w!#\$%&'\*\+\/\=\?\^`\{\|\}~\-]+(:?\.[\w!#\$%&'\*\+\/\=\?\^`\{\|\}~\-]+)*@(?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?$/i; if (!EMAIL_RE.test(email)) { throw new Error("Invalid email format for the passed email:'" + email + "'.Please check the passed email format."); } return email; } /** * Normalizes the given gender and returns acceptable('f' or 'm') gender value * @param {String} [gender] gender value to be normalized. * @return {String} Normalized gender value. */ }, { key: 'normalizeGender', value: function normalizeGender(gender) { gender = gender.replace(/[^a-z]/g, ''); if (gender == 'female' || gender == 'f') { gender = 'f'; } else if (gender == 'male' || gender == 'm') { gender = 'm'; } else return null; return gender; } /** * Normalizes the given phone and returns acceptable phone value * @param {String} [phone] phone number value to be normalized. * @return {String} Normalized phone number value. */ }, { key: 'normalizePhone', value: function normalizePhone(phone_number) { // Remove common characters occuring as part of the phone numbers. phone_number = phone_number.replace(PHONE_NUMBER_IGNORE_CHAR_SET, ''); if (ServerSideUtils.isInternationalPhoneNumber(phone_number)) { phone_number = phone_number.replace(PHONE_NUMBER_DROP_PREFIX_ZEROS, ''); } if (phone_number.length < 7 || phone_number.length > 15) { throw new Error("Invalid phone number format for the passed phone number:'" + phone_number + "'.Please check the passed phone number format."); } return phone_number; } /** * Normalizes the given state and returns acceptable city value * @param {String} [state] state value to be normalized. * @return {String} Normalized state value. */ }, { key: 'normalizeState', value: function normalizeState(state) { state = state.replace(/[0-9\s().-]/g, ''); return state; } /** * Normalizes the given zip/postal code and returns acceptable zip code value * @param {String} [zip] zip value to be normalized. * @return {String} Normalized zip code value. */ }, { key: 'normalizeZip', value: function normalizeZip(zip) { zip = zip.replace(/[\s]/g, ''); // If the zip code '-', we retain just the first part alone. zip = zip.split('-', 1)[0]; if (zip.length < 2) { return null; } return zip; } /** * Boolean method which checks if a given number is represented in international format * @param {String} phone_number that has to be tested. * @return {Boolean} value if a number is represented international format */ }, { key: 'isInternationalPhoneNumber', value: function isInternationalPhoneNumber(phone_number) { // strip up to 2 leading 0s and + phone_number = phone_number.replace(PHONE_NUMBER_DROP_PREFIX_ZEROS, ''); if (phone_number.startsWith('0')) { return false; } if (phone_number.startsWith('1')) { return US_PHONE_NUMBER_REGEX.test(phone_number); } return INTL_PHONE_NUMBER_REGEX.test(phone_number); } /** * Calculates the SHA 256 hash of a given non-null string. * @param {String} [input] String to be hashed * @return {String} SHA 256 Hash of the string */ }, { key: 'tosha256', value: function tosha256(input) { if (input === null) return input; return sha256(input); } }]); return ServerSideUtils; }(); /** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. * */ /** * CustomData represents the Custom Data Parameters of a Server Side Event Request. Use these parameters to send additional data we can use for ads delivery optimization. * @see {@link https://developers.facebook.com/docs/marketing-api/facebook-pixel/server-side-api/parameters#custom} */ var CustomData = function () { /** * @param {Number} value value of the item Eg: 123.45 * @param {String} currency currency involved in the transaction Eg: usd * @param {String} content_name name of the Content Eg: lettuce * @param {String} content_category category of the content Eg: grocery * @param {Array<String>} content_ids list of content unique ids involved in the event * @param {Array<Content>} contents Array of Content Objects. Use {Content} class to define a content. * @param {String} content_type Type of the Content group or Product SKU * @param {String} order_id Unique id representing the order * @param {Number} predicted_ltv Predicted LifeTime Value for the customer involved in the event * @param {Number} num_items Number of items involved * @param {String} search_string query string used for the Search event * @param {String} status Status of the registration in Registration event */ function CustomData(value, currency, content_name, content_category, content_ids, contents, content_type, order_id, predicted_ltv, num_items, search_string, status) { classCallCheck(this, CustomData); this._value = value; this._currency = currency; this._content_name = content_name; this._content_category = content_category; this._content_ids = content_ids; this._contents = contents; this._content_type = content_type; this._order_id = order_id; this._predicted_ltv = predicted_ltv; this._num_items = num_items; this._search_string = search_string; this._status = status; } /** * Gets the value of the custom data. * A numeric value associated with this event. This could be a monetary value or a value in some other metric. * Example: 142.54. */ createClass(CustomData, [{ key: 'setValue', /** * Sets the value of the custom data. * @param {Number} value A numeric value associated with this event. This could be a monetary value or a value in some other metric. * Example: 142.54. */ value: function setValue(value) { this._value = value; return this; } /** * Gets the currency for the custom data. * The currency for the value specified, if applicable. Currency must be a valid ISO 4217 three digit currency code. * Example: 'usd' */ }, { key: 'setCurrency', /** * Sets the currency for the custom data. * @param {String} currency The currency for the value specified, if applicable. Currency must be a valid ISO 4217 three digit currency code. * Example: 'usd' */ value: function setCurrency(currency) { this._currency = currency; return this; } /** * Gets the content name for the custom data. The name of the page or product associated with the event. * The name of the page or product associated with the event. * Example: 'lettuce' */ }, { key: 'setContentName', /** * Sets the content name for the custom data. * @param content_name The name of the page or product associated with the event. * Example: 'lettuce' */ value: function setContentName(content_name) { this._content_name = content_name; return this; } /** * Gets the content category for the custom data. * The category of the content associated with the event. * Example: 'grocery' */ }, { key: 'setContentCategory', /** * Sets the content_category for the custom data. * @param content_category The category of the content associated with the event. * Example: 'grocery' */ value: function setContentCategory(content_category) { this._content_category = content_category; return this; } /** * Gets the content_ids for the custom data. * The content IDs associated with the event, such as product SKUs for items in an AddToCart, represented as Array of string. * If content_type is a product, then your co