facebook-nodejs-business-sdk
Version:
SDK for the Facebook Ads API in Javascript and Node.js
1,816 lines (1,498 loc) • 1.21 MB
JavaScript
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